Skip to content

Commit

Permalink
[engine] Resolve identical Jbehave story names
Browse files Browse the repository at this point in the history
  • Loading branch information
web-flow committed Mar 1, 2023
1 parent bb9562e commit 17d21b5
Show file tree
Hide file tree
Showing 10 changed files with 421 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,18 +16,31 @@

package org.vividus;

import java.util.List;
import java.util.stream.Collectors;

import org.jbehave.core.embedder.PerformableTree;
import org.jbehave.core.model.Story;
import org.jbehave.core.steps.StepCollector.Stage;

public class BatchedPerformableTree extends PerformableTree
{
private static final IdenticalStoryNamesResolver IDENTICAL_STORY_NAMES_RESOLVER = new IdenticalStoryNamesResolver();

private boolean failFast;
private boolean reportBeforeStories;
private boolean reportAfterStories;

@Override
public void performBeforeOrAfterStories(RunContext context, Stage stage)
{
if (Stage.BEFORE.equals(stage))
{
List<Story> batchStories = getRoot().getStories().stream()
.map(PerformableStory::getStory)
.collect(Collectors.toList());
IDENTICAL_STORY_NAMES_RESOLVER.resolveIdenticalNames(batchStories);
}
if (reportBeforeStories && Stage.BEFORE.equals(stage) || Stage.AFTER.equals(stage)
&& (reportAfterStories || failFast && !context.getFailures().isEmpty()))
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright 2019-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.vividus;

import java.io.File;
import java.net.URI;
import java.nio.file.InvalidPathException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.stream.Collectors;

import org.apache.commons.io.FilenameUtils;
import org.jbehave.core.model.Story;

public class IdenticalStoryNamesResolver
{
public void resolveIdenticalNames(List<Story> stories)
{
for (Story story : stories)
{
List<Story> sameNameStories = stories.stream()
.filter(s -> story.getName().equals(s.getName())).collect(Collectors.toList());

if (sameNameStories.size() > 1)
{
Path currentPath = getPathSafely(story.getPath());
List<Path> similarPaths = sameNameStories.stream()
.map(s -> getPathSafely(s.getPath())).collect(Collectors.toList());

Path commonPath = findCommonPath(currentPath, similarPaths);
updateStoryNames(commonPath, sameNameStories);
}
}
}

private Path findCommonPath(Path referencePath, List<Path> paths)
{
Path root = referencePath.getRoot();
Path commonPath = root != null ? root : Paths.get("");

for (Path directory : referencePath)
{
Path possibleCommonPath = commonPath.resolve(directory);
if (paths.stream().anyMatch(path -> !path.startsWith(possibleCommonPath)))
{
break;
}
commonPath = possibleCommonPath;
}
return commonPath;
}

private void updateStoryNames(Path commonPath, List<Story> stories)
{
for (Story story : stories)
{
String prefix = getPrefix(commonPath, getPathSafely(story.getPath()));
if (prefix != null)
{
story.namedAs(prefix + File.separator + story.getName());
}
}
}

private String getPrefix(Path commonPath, Path storyPath)
{
Path prefix = commonPath.relativize(storyPath).getParent();
return prefix != null ? prefix.toString() : null;
}

private Path getPathSafely(String path)
{
try
{
return Paths.get(path);
}
catch (InvalidPathException e)
{
Path resultPath = Paths.get(URI.create(FilenameUtils.getFullPath(path)));
return Paths.get(resultPath.toString(), FilenameUtils.getName(path));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright 2019-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.vividus;

import java.io.File;

import org.apache.commons.lang3.StringUtils;
import org.jbehave.core.embedder.PerformableTree;
import org.jbehave.core.io.StoryLocation;
import org.jbehave.core.reporters.FilePrintStreamFactory.ResolveToSimpleName;
import org.vividus.context.RunContext;

public class ResolveToUniqueSimpleName extends ResolveToSimpleName
{
private final RunContext runContext;
private final PerformableTree performableTree;

public ResolveToUniqueSimpleName(RunContext runContext, PerformableTree performableTree)
{
this.runContext = runContext;
this.performableTree = performableTree;
}

@Override
public String resolveName(StoryLocation storyLocation, String extension)
{
String storyPath = storyLocation.getStoryPath();

if ("BeforeStories".equals(storyPath) || "AfterStories".equals(storyPath))
{
return super.resolveName(storyLocation, extension);
}

String storyName = performableTree.getRoot().getStories().stream()
.filter(p -> p.getStory().getPath().equals(storyPath))
.findFirst().get().getStory().getName();
String storyNameOutput = StringUtils.removeEnd(storyName, "story").replace(File.separator, ".") + extension;

return runContext.getRunningBatchKey() + File.separator + storyNameOutput;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@

<bean id="testResourceLoader" class="org.vividus.resource.TestResourceLoader" />

<bean id="batchedPerformableTree" class="org.vividus.BatchedPerformableTree" />

<bean id="storyReporterBuilder" class="org.vividus.ExtendedStoryReporterBuilder">
<property name="reportFailureTrace" value="true" />
<property name="storyReporter" ref="storyReporter" />
<property name="codeLocation" value="${bdd.report-directory}" />
<property name="formats" value="${bdd.configuration.formats}" />
<property name="pathResolver">
<bean class="org.jbehave.core.reporters.FilePrintStreamFactory.ResolveToSimpleName" />
<bean class="org.vividus.ResolveToUniqueSimpleName"/>
</property>
</bean>

Expand Down Expand Up @@ -83,9 +85,7 @@
<property name="embedderMonitor">
<bean class="org.vividus.log.LoggingEmbedderMonitor" />
</property>
<property name="performableTree">
<bean class="org.vividus.BatchedPerformableTree" />
</property>
<property name="performableTree" ref="batchedPerformableTree"/>
<property name="generateViewAfterBatches" value="${bdd.generate-view-after-batches}" />
</bean>

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2019-2021 the original author or authors.
* Copyright 2019-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,13 +16,20 @@

package org.vividus;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

import java.io.File;

import org.jbehave.core.embedder.PerformableTree.PerformableRoot;
import org.jbehave.core.embedder.PerformableTree.PerformableStory;
import org.jbehave.core.embedder.PerformableTree.RunContext;
import org.jbehave.core.failures.BatchFailures;
import org.jbehave.core.model.Story;
import org.jbehave.core.reporters.StoryReporter;
import org.jbehave.core.steps.StepCollector.Stage;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -89,6 +96,33 @@ void testPerformBeforeOrAfterStoriesPerformNoneForAfter()
testPerformBeforeOrAfterStoriesPerformNone(Stage.AFTER);
}

@Test
void shouldResolveIdenticalNamesBeforeStories()
{
PerformableRoot root = batchedPerformableTree.getRoot();
Story storyName = new Story("file:/tests/name.story");
Story storyFolderName = new Story("file:/tests/folder/name.story");
root.add(createPerformableStory(storyName));
root.add(createPerformableStory(storyFolderName));
batchedPerformableTree.performBeforeOrAfterStories(runContext, Stage.BEFORE);
assertEquals("folder" + File.separator + "name.story", storyFolderName.getName());
}

@Test
void shouldNotInteractWithStoriesAfterStories()
{
PerformableRoot root = batchedPerformableTree.getRoot();
Story storyMock = mock(Story.class);
PerformableStory performableStoryMock = mock(PerformableStory.class);
when(performableStoryMock.getStory()).thenReturn(storyMock);
when(storyMock.getPath()).thenReturn("");
root.add(performableStoryMock);
verify(storyMock).getPath();

batchedPerformableTree.performBeforeOrAfterStories(runContext, Stage.AFTER);
verifyNoMoreInteractions(storyMock);
}

private BatchFailures getFailures()
{
BatchFailures failures = new BatchFailures();
Expand All @@ -106,4 +140,9 @@ private void mockRunContext()
{
when(runContext.reporter()).thenReturn(mock(StoryReporter.class));
}

private PerformableStory createPerformableStory(Story story)
{
return new PerformableStory(story, null, false);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2019-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.vividus;

import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

import java.io.File;
import java.util.List;

import org.jbehave.core.model.Story;
import org.junit.jupiter.api.Test;

class IdenticalStoryNamesResolverTests
{
private static final String SEPARATOR = File.separator;
private static final String DIRECTORY = "directory";
private static final String NAME = "name.story";
private static final String ANOTHER_NAME = "nomen.story";

private static final IdenticalStoryNamesResolver NAMES_RESOLVER = new IdenticalStoryNamesResolver();

@Test
void shouldResolveIdenticalNames()
{
Story storyName = new Story("file:/tests/name.story");
Story storyDirectoryName = new Story("file:/tests/directory/name.story");
Story storyDirectoryAnotherName = new Story("file:/tests/directory/nomen.story");
Story storyDirectoryDirectoryName = new Story("file:/tests/directory/directory/name.story");
Story storyDirectory1Name = new Story("file:/tests/directory1/name.story");
List<Story> batchStories = List.of(storyName, storyDirectoryName, storyDirectoryAnotherName,
storyDirectoryDirectoryName, storyDirectory1Name);

NAMES_RESOLVER.resolveIdenticalNames(batchStories);
assertAll(
() -> assertEquals(NAME, storyName.getName()),
() -> assertEquals(DIRECTORY + SEPARATOR + NAME, storyDirectoryName.getName()),
() -> assertEquals(ANOTHER_NAME, storyDirectoryAnotherName.getName()),
() -> assertEquals(DIRECTORY + SEPARATOR + DIRECTORY + SEPARATOR + NAME,
storyDirectoryDirectoryName.getName()),
() -> assertEquals("directory1" + SEPARATOR + NAME, storyDirectory1Name.getName())
);
}

@Test
void shouldNotProcessDifferentNames()
{
Story storyMock1 = mock(Story.class);
Story storyMock2 = mock(Story.class);
when(storyMock1.getPath()).thenReturn("file:/tests/name1.story");
when(storyMock2.getPath()).thenReturn("file:/tests/name2.story");
when(storyMock1.getName()).thenReturn("name1");
when(storyMock2.getName()).thenReturn("name2");
List<Story> batchStories = List.of(storyMock1, storyMock2);

NAMES_RESOLVER.resolveIdenticalNames(batchStories);
verify(storyMock1, times(4)).getName();
verify(storyMock2, times(4)).getName();
verifyNoMoreInteractions(storyMock1, storyMock2);
}
}
Loading

0 comments on commit 17d21b5

Please sign in to comment.