Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UmlStateMachineModelFactory - inconsistency between StateMachineModel and StateMachine - duplicated transitions created! #1141

Open
pdalfarr opened this issue Mar 4, 2024 · 1 comment
Labels
status/need-triage Team needs to triage and take a first look

Comments

@pdalfarr
Copy link

pdalfarr commented Mar 4, 2024

Using simple-root-regions.uml to perform some tests, I encountered an unexpected situation where the 'transitions per regions' are not consistent between StateMachineModel and StateMachine.

I am using the simple-root-regions.uml file from this repository to create a StateMachineModel.

This StateMachineModel contains:

  • Region1 with 1 'non-initial' transition "S1->S2"
  • Region2 with 1 'non-initial' transition "S3->S4"

When I create a StateMachine based on this StateMachineModel, I end up with a StateMachine containing:

  • Region1 with 1 'non-initial' transition "S1->S2"
  • Region2 with 1 'non-initial' transition "S3->S4" AND the "S1->S2" again! "S1->S2" transition is duplicated in Region 2.

So it seems the "S1->S2" from Region1 has been duplicated in Regon2 as illustrated below.

Unit test available :-)

I created a unit test avalable here to help you troubleshot the issue.
This unit test does compare a StateMachineModel and a StateMachine regarding:

  • the states per region (this part is OK)
  • the transitions per region (this part is NOT OK becasue of the duplicated transition "S1->S2")

simple-root-regions

@github-actions github-actions bot added the status/need-triage Team needs to triage and take a first look label Mar 4, 2024
@pdalfarr
Copy link
Author

pdalfarr commented Mar 4, 2024

Adding the unit test in this ticket.

So the last assertion does fail:

assertThat(transitionsOfRegion2InStateMachine).isEqualTo(transitionsOfRegion2InStateMachineModel);

Unit test to add in UmlStateMachineModelFactoryTests.java :

	public void testStateMachineVsStateMachineModelConsistency() {
		context.refresh();
		Resource model1 = new ClassPathResource("org/springframework/statemachine/uml/simple-root-regions.uml");
		UmlStateMachineModelFactory builder = new UmlStateMachineModelFactory(model1);
		builder.setBeanFactory(context);
		assertThat(model1.exists()).isTrue();
		StateMachineModel<String, String> stateMachineModel = builder.build();

		try {
			// build statemachine from model
			UmlStateMachineModelFactory umlStateMachineModelFactory = new UmlStateMachineModelFactory(("classpath:org/springframework/statemachine/uml/simple-root-regions.uml"));
			StateMachineBuilder.Builder<String, String> stateMachineBuilder = StateMachineBuilder.builder();
			stateMachineBuilder.configureModel().withModel().factory(umlStateMachineModelFactory);
			stateMachineBuilder.configureConfiguration().withConfiguration();
			StateMachine<String, String> stateMachine = stateMachineBuilder.build();

			// get the "root" state of this state machines
			State<String, String> rootState = stateMachine.getStates().stream().findFirst().get();
			assertThat(rootState).isInstanceOf(RegionState.class);
			RegionState<String, String> rootRegionState = ((RegionState<String, String>) rootState);

			// compare statemachine and stateMachineModel

			// states in Region1
			AbstractStateMachine region1InStatemachine = (AbstractStateMachine)
					((List) rootRegionState.getRegions()).stream()
							.filter(region -> ((AbstractStateMachine) region).getId().contains("Region1"))
							.findFirst().get();

			List statesOfRegion1InStateMachine = region1InStatemachine.getStates().stream()
					.map(o -> ((State) o).getId().toString())
					.sorted().toList();

			List<String> statesOfRegion1InStateMachineModel = stateMachineModel.getStatesData().getStateData().stream()
					.filter(stateData -> "Region1".equals(stateData.getRegion().toString()))
					.map(stateData -> stateData.getState().toString())
					.sorted().toList();

			assertThat(statesOfRegion1InStateMachine).isEqualTo(statesOfRegion1InStateMachineModel);

			// states in Region2
			AbstractStateMachine region2InStatemachine = (AbstractStateMachine)
					((List) rootRegionState.getRegions()).stream()
							.filter(region -> ((AbstractStateMachine) region).getId().contains("Region2"))
							.findFirst().get();

			List statesOfRegion2InStateMachine = region2InStatemachine.getStates().stream()
					.map(o -> ((State) o).getId().toString())
					.sorted().toList();

			List<String> statesOfRegion2InStateMachineModel = stateMachineModel.getStatesData().getStateData().stream()
					.filter(stateData -> "Region2".equals(stateData.getRegion().toString()))
					.map(stateData -> stateData.getState().toString())
					.sorted().toList();

			assertThat(statesOfRegion2InStateMachine).isEqualTo(statesOfRegion2InStateMachineModel);

			// transitions in Region1
			List transitionsOfRegion1InStateMachine = region1InStatemachine.getTransitions().stream()
					.map(o -> ((Transition) o).getSource().getId().toString() + "->" + ((Transition) o).getTarget().getId().toString())
					.sorted().toList();

			List<String> transitionsOfRegion1InStateMachineModel = stateMachineModel.getTransitionsData().getTransitions().stream()
					// let's exclude "initial" transition
					.filter(transitionData -> !transitionData.getSource().startsWith("initial"))
					.filter(transitionData -> statesOfRegion1InStateMachine.contains(transitionData.getSource())
							|| statesOfRegion1InStateMachine.contains(transitionData.getTarget()))
					.map(transitionData -> transitionData.getSource() + "->" + transitionData.getTarget())
					.sorted().toList();

			assertThat(transitionsOfRegion1InStateMachine).isEqualTo(transitionsOfRegion1InStateMachineModel);

			// transitions in Region2
			List transitionsOfRegion2InStateMachine = region2InStatemachine.getTransitions().stream()
					.map(o -> ((Transition) o).getSource().getId().toString() + "->" + ((Transition) o).getTarget().getId().toString())
					.sorted().toList();

			List<String> transitionsOfRegion2InStateMachineModel = stateMachineModel.getTransitionsData().getTransitions().stream()
					// let's exclude "initial" transition
					.filter(transitionData -> !transitionData.getSource().startsWith("initial"))
					.filter(transitionData -> statesOfRegion2InStateMachine.contains(transitionData.getSource())
							|| statesOfRegion2InStateMachine.contains(transitionData.getTarget()))
					.map(transitionData -> transitionData.getSource() + "->" + transitionData.getTarget())
					.sorted().toList();

			// WOW! this is failing! Why is transition "S1->S2" present in both Region1 AND Region2 ?!?
			// Does this indicates an issue in UmlStateMachineModelFactory ???
			// Expected :["S1->S2"]
			// Actual   :["S1->S2", "S3->S4"]
			assertThat(transitionsOfRegion2InStateMachine).isEqualTo(transitionsOfRegion2InStateMachineModel);

		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
status/need-triage Team needs to triage and take a first look
Projects
None yet
Development

No branches or pull requests

1 participant