Skip to content

Commit

Permalink
Theme, capability and feature-level reports show up correctly
Browse files Browse the repository at this point in the history
  • Loading branch information
jan-molak committed Jan 4, 2024
1 parent 71bf9c8 commit 37a3906
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
*/
public class AggregateRequirements implements Requirements {

private final RequirementsService requirementsService;
private final BaseRequirementsService requirementsService;
private final RequirementsOutcomeFactory requirementsOutcomeFactory;

public AggregateRequirements(Path jsonOutcomes, String featureFilesDirectory) {
Expand All @@ -35,7 +35,7 @@ public AggregateRequirements(Path jsonOutcomes, String featureFilesDirectory, En
environmentVariables,
new SystemPropertiesIssueTracking(environmentVariables),
new ReportNameProvider(NO_CONTEXT, ReportType.HTML, this.requirementsService),
featureFilesDirectory
this.requirementsService
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,12 @@ public static DefaultCapabilityTypes instance() {
}

private final static String JAVASCRIPT_SPEC_FILE_EXTENSION_PATTERN =
".*" +
"\\.(spec|test|integration|it|e2e|spec\\.e2e|spec-e2e)" + // Consider only test files...
"\\.(jsx?|mjsx?|cjsx?|tsx?|mtsx?|ctsx?)$"; // implemented in either JavaScript or TypeScript

private final static String JAVASCRIPT_SPEC_FILE_NAME_PATTERN =
"^(?!.*/(node_modules|jspm_packages|web_modules)/)" + // Ignore external dependencies
".*" +
JAVASCRIPT_SPEC_FILE_EXTENSION_PATTERN;

public void clear() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;

Expand Down Expand Up @@ -149,6 +150,7 @@ private RequirementsTagProvider withoutAddingParents() {
* at the working directory.
*/
public List<Requirement> getRequirements() {
// todo consider refactoring to use RequirementsCache
if (requirements == null) {
synchronized (this) {
if (requirements == null) { // double-checked locking
Expand Down Expand Up @@ -180,14 +182,15 @@ private Map<PathElements, Requirement> indexByPath(List<Requirement> requirement

private Stream<Requirement> capabilitiesAndStoriesIn(String path) {
File rootDirectory = new File(path);
if (rootDirectory.exists()) {
return Stream.concat(
loadCapabilitiesFrom(rootDirectory.listFiles(thatAreFeatureDirectories())),
loadStoriesFrom(rootDirectory.listFiles(thatAreStories()))
);
} else {

if (! rootDirectory.exists()) {
return NO_REQUIREMENTS.stream();
}

return Stream.concat(
loadCapabilitiesFrom(rootDirectory.listFiles(thatAreFeatureDirectories())),
loadStoriesFrom(rootDirectory.listFiles(thatAreStories()))
);
}

private int maxDirectoryDepthIn(Set<String> directoryPaths) {
Expand Down Expand Up @@ -583,13 +586,15 @@ public Requirement readRequirementFrom(File requirementDirectory) {
requirementDirectory = normalised(requirementDirectory);
java.util.Optional<RequirementDefinition> requirementNarrative = narrativeReader.loadFrom(requirementDirectory, Math.max(0, level));

if (requirementNarrative.isPresent()) {
return requirementWithNarrative(requirementDirectory,
humanReadableVersionOf(requirementDirectory.getName()),
requirementNarrative.get());
} else {
if (! requirementNarrative.isPresent()) {
return requirementFromDirectoryName(requirementDirectory);
}

return requirementWithNarrative(
requirementDirectory,
humanReadableVersionOf(requirementDirectory.getName()),
requirementNarrative.get()
);
}

private final Set<File> invalidFeatureFiles = new HashSet<>();
Expand Down Expand Up @@ -816,20 +821,24 @@ private List<Requirement> readChildrenFrom(File requirementDirectory) {
private boolean childrenExistFor(String path) {
if (hasSubdirectories(path)) {
return true;
} else if (hasFeatureOrStoryFiles(path)) {
} else if (hasFeatureStoryOrJavaScriptSpecFiles(path)) {
return true;
} else {
return classpathResourceExistsFor(path);
}
}

private boolean hasFeatureOrStoryFiles(String path) {
private boolean hasFeatureStoryOrJavaScriptSpecFiles(String path) {
File requirementDirectory = new File(path);
if (requirementDirectory.isDirectory()) {
return ((requirementDirectory.list(storyFiles()).length > 0) || (requirementDirectory.list(featureFiles()).length > 0));
} else {
if (! requirementDirectory.isDirectory()) {
return false;
}

boolean hasStoryFiles = requirementDirectory.list(storyFiles()).length > 0;
boolean hasFeatureFiles = requirementDirectory.list(featureFiles()).length > 0;
boolean hasJavaScriptSpecFiles = requirementDirectory.list(javascriptSpecFiles()).length > 0;

return hasStoryFiles || hasFeatureFiles || hasJavaScriptSpecFiles;
}

private FilenameFilter storyFiles() {
Expand All @@ -840,6 +849,10 @@ private FilenameFilter featureFiles() {
return (dir, name) -> name.endsWith(".feature");
}

private FilenameFilter javascriptSpecFiles() {
return (dir, name) -> name.matches(JAVASCRIPT_SPEC_FILE_EXTENSION_PATTERN);
}

private boolean classpathResourceExistsFor(String path) {
return getClass().getResource(resourcePathFor(path)) != null;
}
Expand Down Expand Up @@ -869,11 +882,15 @@ private String getTitleFromNarrativeOrDirectoryName(RequirementDefinition requir
}

private FileFilter thatAreFeatureDirectories() {
return file -> !file.getName().startsWith(".") && storyOrFeatureFilesExistIn(file);
return file -> !file.getName().startsWith(".") && storyFeatureFilesOrJavaScriptSpecsExistIn(file);
}

private boolean storyOrFeatureFilesExistIn(File directory) {
return startingAt(normalised(directory)).containsFiles(thatAreStories(), thatAreNarratives());
private boolean storyFeatureFilesOrJavaScriptSpecsExistIn(File directory) {
return startingAt(normalised(directory)).containsFiles(
thatAreStories(),
thatAreNarratives(),
thatAreJavaScriptSpecs()
);
}

private FileFilter thatAreStories() {
Expand All @@ -887,6 +904,26 @@ private FileFilter thatAreStories() {
};
}

private FileFilter thatAreJavaScriptSpecs() {
return file -> {
String filename = file.getName().toLowerCase();
return JAVASCRIPT_SPEC_FILE_PATTERN.matcher(filename).matches();
};
}

// todo: extract from DefaultCapabilityTypes
private final static String JAVASCRIPT_SPEC_FILE_EXTENSION_PATTERN =
".*" +
"\\.(spec|test|integration|it|e2e|spec\\.e2e|spec-e2e)" + // Consider only test files...
"\\.(jsx?|mjsx?|cjsx?|tsx?|mtsx?|ctsx?)$"; // implemented in either JavaScript or TypeScript

private final static String JAVASCRIPT_SPEC_FILE_NAME_PATTERN =
"^(?!.*/(node_modules|jspm_packages|web_modules)/)" + // Ignore external dependencies
JAVASCRIPT_SPEC_FILE_EXTENSION_PATTERN;

private final static Pattern JAVASCRIPT_SPEC_FILE_PATTERN = Pattern.compile(JAVASCRIPT_SPEC_FILE_NAME_PATTERN);


private FileFilter thatAreNarratives() {
return file -> file.getName().equalsIgnoreCase("narrative.txt")
|| file.getName().equalsIgnoreCase("narrative.md")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import net.thucydides.model.requirements.model.Requirement;
import net.thucydides.model.requirements.model.RequirementsConfiguration;
import net.thucydides.model.util.EnvironmentVariables;
import net.thucydides.model.util.Inflector;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;

Expand Down Expand Up @@ -111,39 +112,39 @@ private List<Requirement> loadRequirements() {
requirementsDirectory = outputDirectory;
}
// If no output directory exists (yet), the test run is still in progress, so don't bother reading the requirements yet.
if (requirementsDirectory.exists()) {
List<TestOutcome> outcomes = loader.loadFrom(requirementsDirectory);
if (! requirementsDirectory.exists()) {
return new ArrayList<>();
}

int maxRequirementsDepth = getMaxRequirementsDepthFrom(outcomes);
List<TestOutcome> outcomes = loader.loadFrom(requirementsDirectory);

// Bottom-level requirements
Map<PathElements, Requirement> leafLevelRequirements = getLeafLevelRequirementsFrom(outcomes);
int maxRequirementsDepth = getMaxRequirementsDepthFrom(outcomes);

Set<PathElements> leafPathElements = leafLevelRequirements.keySet();
// Bottom-level requirements
Map<PathElements, Requirement> leafLevelRequirements = getLeafLevelRequirementsFrom(outcomes);

Map<PathElements, Requirement> requirementsByPath = new HashMap<>();
Set<PathElements> leafPathElements = leafLevelRequirements.keySet();

// Non-leaf requirements indexed by path
findPathElementsIn(outcomes).forEach(pathElements -> processPathElements(pathElements, maxRequirementsDepth, leafPathElements, leafLevelRequirements, requirementsByPath));
Map<PathElements, Requirement> requirementsByPath = new HashMap<>();

Collection<Requirement> allRequirements = requirementsByPath.values();
// Non-leaf requirements indexed by path
findPathElementsIn(outcomes).forEach(pathElements -> processPathElements(pathElements, maxRequirementsDepth, leafPathElements, leafLevelRequirements, requirementsByPath));

// Use the map to update the leaf requirements
updateParentFieldsIn(requirementsByPath, allRequirements);
Collection<Requirement> allRequirements = requirementsByPath.values();

// Make an alias for any leaf requirements that also appear in the non-leaf requirements.
// Use the map to update the leaf requirements
updateParentFieldsIn(requirementsByPath, allRequirements);

populateChildren(requirementsByPath, allRequirements);
// Make an alias for any leaf requirements that also appear in the non-leaf requirements.

RequirementCache.getInstance().indexRequirements(requirementsByPath);
populateChildren(requirementsByPath, allRequirements);

// Return a list of the top-level or leaf requirements with no parent elements
return allRequirements.stream()
.filter(requirement -> StringUtils.isEmpty(requirement.getParent()))
.collect(Collectors.toList());
} else {
return new ArrayList<>();
}
RequirementCache.getInstance().indexRequirements(requirementsByPath);

// Return a list of the top-level or leaf requirements with no parent elements
return allRequirements.stream()
.filter(requirement -> StringUtils.isEmpty(requirement.getParent()))
.collect(Collectors.toList());
}

private void processPathElements(PathElements pathElements, int maxRequirementsDepth, Set<PathElements> leafPathElements,
Expand Down Expand Up @@ -282,7 +283,7 @@ private Requirement nonLeafRequirementFrom(PathElements relativePath, int maxReq

String requirementType = requirementsConfiguration.getRequirementType(relativePath.size() - 1, maxRequirementsDepth);

return Requirement.named(requirementLeaf.getName())
return Requirement.named(humanReadableVersionOf(requirementLeaf.getName()))
.withId(StringUtils.isBlank(path) ? requirementLeaf.getName() : path)
.withType(requirementType)
.withNarrative("")
Expand All @@ -291,6 +292,15 @@ private Requirement nonLeafRequirementFrom(PathElements relativePath, int maxReq
.withParent(parentPath);
}

// fixme: duplicated from AbstractRequirementsTagProvider
// as it is not the parent of this class (should it be?).
// This is to make the discovered requirements compatible with the results
// of the FileSystemRequirementsTagProvider.
protected String humanReadableVersionOf(String name) {
String underscoredName = Inflector.getInstance().underscore(name);
return Inflector.getInstance().humanize(underscoredName);
}

private PathElements relativePathFrom(PathElements pathElements) {
List<String> rootPackageElements = rootPackageElements();
List<PathElement> relativePathElements = new ArrayList<>(pathElements);
Expand All @@ -311,7 +321,7 @@ private List<String> rootPackageElements() {
private Requirement requirementFrom(Story userStory) {//
PathElement requirementLeaf = userStory.getPathElements().get(userStory.getPathElements().size() - 1);
String parent = (userStory.getPathElements().getParent() != null) ? userStory.getPathElements().getParent().toString() : "";
return Requirement.named(requirementLeaf.getName())
return Requirement.named(humanReadableVersionOf(requirementLeaf.getName()))
.withId(userStory.getPathElements().toString())
.withType(userStory.getType())
.withNarrative(userStory.getNarrative())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package net.thucydides.model.requirements.reports;

import net.serenitybdd.model.collect.NewList;
import net.thucydides.model.issues.IssueTracking;
import net.thucydides.model.issues.SystemPropertiesIssueTracking;
import net.thucydides.model.reports.TestOutcomes;
import net.thucydides.model.reports.html.ReportNameProvider;
import net.thucydides.model.requirements.AggregateRequirementsService;
import net.thucydides.model.requirements.BaseRequirementsService;
import net.thucydides.model.requirements.FileSystemRequirementsTagProvider;
import net.thucydides.model.requirements.RequirementsTagProvider;
import net.thucydides.model.requirements.model.Requirement;
Expand All @@ -16,48 +16,51 @@

public class FileSystemRequirmentsOutcomeFactory implements RequirementsOutcomeFactory {

private final static String EMPTY_OVERVIEW = "";

private final IssueTracking issueTracking;
private final EnvironmentVariables environmentVariables;
private final FileSystemRequirementsTagProvider tagProvider;
private final BaseRequirementsService requirementsService;
private final ReportNameProvider reportNameProvider;

private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemRequirmentsOutcomeFactory.class);

public FileSystemRequirmentsOutcomeFactory(EnvironmentVariables environmentVariables) {
this(environmentVariables,
new SystemPropertiesIssueTracking(),
new ReportNameProvider());
public FileSystemRequirmentsOutcomeFactory(final EnvironmentVariables environmentVariables,
final IssueTracking issueTracking,
final ReportNameProvider reportNameProvider,
final String rootDirectoryPath) {
this(
environmentVariables,
issueTracking,
reportNameProvider,
new AggregateRequirementsService(
environmentVariables,
new FileSystemRequirementsTagProvider(rootDirectoryPath, environmentVariables)
)
);
}

public FileSystemRequirmentsOutcomeFactory(EnvironmentVariables environmentVariables,
IssueTracking issueTracking,
ReportNameProvider reportNameProvider) {
this.issueTracking = issueTracking;
public FileSystemRequirmentsOutcomeFactory(final EnvironmentVariables environmentVariables,
final IssueTracking issueTracking,
final ReportNameProvider reportNameProvider,
final BaseRequirementsService requirementsService) {
this.environmentVariables = environmentVariables;
this.tagProvider = new FileSystemRequirementsTagProvider(environmentVariables);
this.reportNameProvider = reportNameProvider;
}

public FileSystemRequirmentsOutcomeFactory(EnvironmentVariables environmentVariables,
IssueTracking issueTracking,
ReportNameProvider reportNameProvider,
String rootDirectoryPath) {
this.issueTracking = issueTracking;
this.environmentVariables = environmentVariables;
this.tagProvider = new FileSystemRequirementsTagProvider(environmentVariables, rootDirectoryPath);
this.reportNameProvider = reportNameProvider;
this.requirementsService = requirementsService;
}

public RequirementsOutcomes buildRequirementsOutcomesFrom(TestOutcomes testOutcomes) {
List<Requirement> allRequirements = tagProvider.getRequirements();
List<Requirement> allRequirements = requirementsService.getRequirements();
LOGGER.debug("Loaded requirements from file system = " + allRequirements);
return new RequirementsOutcomes(allRequirements,
testOutcomes,
issueTracking,
environmentVariables,
NewList.<RequirementsTagProvider>of(tagProvider),
requirementsService.getRequirementsTagProviders(),
reportNameProvider,
tagProvider.getOverview().orElse(""));
getOverview()
);
}

public RequirementsOutcomes buildRequirementsOutcomesFrom(Requirement parentRequirement,
Expand All @@ -68,8 +71,19 @@ public RequirementsOutcomes buildRequirementsOutcomesFrom(Requirement parentRequ
testOutcomes,
issueTracking,
environmentVariables,
NewList.of(tagProvider),
requirementsService.getRequirementsTagProviders(),
reportNameProvider,
"");
EMPTY_OVERVIEW);
}

private String getOverview() {
// fixme: this is a bit of a hacky way to retrieve the overview that could potentially be improved
for (RequirementsTagProvider provider : requirementsService.getRequirementsTagProviders()) {
if (provider instanceof FileSystemRequirementsTagProvider) {
return provider.getOverview().orElse(EMPTY_OVERVIEW);
}
}

return EMPTY_OVERVIEW;
}
}
Loading

0 comments on commit 37a3906

Please sign in to comment.