From da5313e3765e9b48c0086c296f2b2b4df7fe1380 Mon Sep 17 00:00:00 2001 From: Jakub Kubrynski Date: Sun, 7 Feb 2016 01:43:05 +0100 Subject: [PATCH] Implement new way of scanning for contract files. Add support for generating ignored tests. Fix #28 #53 --- .travis.yml | 2 +- .../accurest/SingleTestGenerator.groovy | 11 +-- .../io/codearte/accurest/TestGenerator.groovy | 64 +++++++++--------- .../accurest/builder/MethodBuilder.groovy | 13 +++- .../config/AccurestConfigProperties.groovy | 41 ++++++++++++ .../codearte/accurest/config/TestMode.groovy | 15 ++++- .../io/codearte/accurest/file/Contract.groovy | 17 +++++ .../accurest/file/ContractFileScanner.groovy | 67 +++++++++++++++++++ .../accurest/GeneratorScannerSpec.groovy | 38 ++--------- .../io/codearte/accurest/MainTest.groovy | 2 +- .../file/ContractFileScannerSpec.groovy | 30 +++++++++ 11 files changed, 226 insertions(+), 74 deletions(-) create mode 100644 accurest-core/src/main/groovy/io/codearte/accurest/file/Contract.groovy create mode 100644 accurest-core/src/main/groovy/io/codearte/accurest/file/ContractFileScanner.groovy create mode 100644 accurest-core/src/test/groovy/io/codearte/accurest/file/ContractFileScannerSpec.groovy diff --git a/.travis.yml b/.travis.yml index bdf2cb280b..220272e2ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,4 +12,4 @@ cache: - $HOME/.m2 install: ./gradlew assemble -script: ./gradlew check funcTest --stacktrace --info --continue +script: ./gradlew clean check funcTest --stacktrace --info --continue diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/SingleTestGenerator.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/SingleTestGenerator.groovy index fb2d135419..d950bd345c 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/SingleTestGenerator.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/SingleTestGenerator.groovy @@ -5,6 +5,7 @@ import io.codearte.accurest.builder.ClassBuilder import io.codearte.accurest.config.AccurestConfigProperties import io.codearte.accurest.config.TestFramework import io.codearte.accurest.config.TestMode +import io.codearte.accurest.file.Contract import static io.codearte.accurest.builder.ClassBuilder.createClass import static io.codearte.accurest.builder.MethodBuilder.createTestMethod @@ -19,7 +20,7 @@ class SingleTestGenerator { } @PackageScope - String buildClass(List listOfFiles, String className, String classPackage) { + String buildClass(Collection listOfFiles, String className, String classPackage) { ClassBuilder clazz = createClass(capitalize(className), classPackage, configProperties) @@ -29,6 +30,10 @@ class SingleTestGenerator { } } + if (listOfFiles.ignored.find {it}) { + clazz.addImport("org.junit.Ignore") + } + if (configProperties.staticImports) { configProperties.staticImports.each { clazz.addStaticImport(it) @@ -50,9 +55,7 @@ class SingleTestGenerator { } if (configProperties.ruleClassForTests) { - - clazz.addImport('org.junit.Rule') - .addRule(configProperties.ruleClassForTests) + clazz.addImport('org.junit.Rule').addRule(configProperties.ruleClassForTests) } addJsonPathRelatedImports(clazz) diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/TestGenerator.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/TestGenerator.groovy index bcd7ac3350..6c97397b5a 100755 --- a/accurest-core/src/main/groovy/io/codearte/accurest/TestGenerator.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/TestGenerator.groovy @@ -1,14 +1,20 @@ package io.codearte.accurest +import com.google.common.collect.Multimap import groovy.transform.PackageScope import io.codearte.accurest.config.AccurestConfigProperties -import org.apache.commons.io.FilenameUtils -import org.codehaus.plexus.util.DirectoryScanner +import io.codearte.accurest.file.Contract +import io.codearte.accurest.file.ContractFileScanner +import org.apache.commons.lang3.StringUtils import java.nio.charset.StandardCharsets +import java.nio.file.Path import java.util.concurrent.atomic.AtomicInteger -import static io.codearte.accurest.util.NamesUtil.* +import static io.codearte.accurest.util.NamesUtil.afterLast +import static io.codearte.accurest.util.NamesUtil.beforeLast +import static io.codearte.accurest.util.NamesUtil.convertIllegalPackageChars +import static io.codearte.accurest.util.NamesUtil.directoryToPackage /** * @author Jakub Kubrynski @@ -16,10 +22,12 @@ import static io.codearte.accurest.util.NamesUtil.* class TestGenerator { private final AccurestConfigProperties configProperties + private final String DEFAULT_CLASS_PREFIX = "Accurest" + private AtomicInteger counter = new AtomicInteger() private SingleTestGenerator generator private FileSaver saver - private DirectoryScanner directoryScanner + private ContractFileScanner contractFileScanner TestGenerator(AccurestConfigProperties accurestConfigProperties) { this(accurestConfigProperties, new SingleTestGenerator(accurestConfigProperties), @@ -33,9 +41,9 @@ class TestGenerator { } this.generator = generator this.saver = saver - this.directoryScanner = new DirectoryScanner() - directoryScanner.setExcludes(configProperties.getIgnoredFiles() as String[]) - directoryScanner.setBasedir(configProperties.contractsDslDir) + contractFileScanner = new ContractFileScanner(configProperties.contractsDslDir, + configProperties.excludedFiles as Set, + configProperties.ignoredFiles as Set) } int generate() { @@ -45,31 +53,28 @@ class TestGenerator { @PackageScope void generateTestClasses(final String basePackageName) { - directoryScanner.scan() - directoryScanner.getIncludedDirectories() - .each { String includedDirectoryRelativePath -> - processIncludedDirectory(includedDirectoryRelativePath, basePackageName) - + Multimap contracts = contractFileScanner.findContracts() + contracts.asMap().entrySet().each { + Map.Entry> entry -> processIncludedDirectory(relativizeContractPath(entry), entry.getValue(), basePackageName) } } + private String relativizeContractPath(Map.Entry> entry) { + Path relativePath = configProperties.contractsDslDir.toPath().relativize(entry.getKey()) + if (StringUtils.isBlank(relativePath.toString())) { + return DEFAULT_CLASS_PREFIX + } + return relativePath.toString() + } + private void processIncludedDirectory( - final String includedDirectoryRelativePath, final String basePackageNameForClass) { - if (!includedDirectoryRelativePath.isEmpty()) { - List filesToClass = directoryScanner.includedFiles. - grep { String includedFile -> - return normalizePath(includedFile).matches(normalizePath(includedDirectoryRelativePath + File.separator) + "[A-Za-z0-9_]*\\.groovy") - } - .collect { - return new File(configProperties.contractsDslDir, it) - } - if (filesToClass.size()) { - def className = afterLast(includedDirectoryRelativePath, File.separator) + resolveNameSuffix() - def packageName = buildPackage(basePackageNameForClass, includedDirectoryRelativePath) - def classBytes = generator.buildClass(filesToClass, className, packageName).getBytes(StandardCharsets.UTF_8) - saver.saveClassFile(className, basePackageNameForClass, convertIllegalPackageChars(includedDirectoryRelativePath), classBytes) - counter.incrementAndGet() - } + final String includedDirectoryRelativePath, Collection contracts, final String basePackageNameForClass) { + if (contracts.size()) { + def className = afterLast(includedDirectoryRelativePath.toString(), File.separator) + resolveNameSuffix() + def packageName = buildPackage(basePackageNameForClass, includedDirectoryRelativePath) + def classBytes = generator.buildClass(contracts, className, packageName).getBytes(StandardCharsets.UTF_8) + saver.saveClassFile(className, basePackageNameForClass, convertIllegalPackageChars(includedDirectoryRelativePath.toString()), classBytes) + counter.incrementAndGet() } } @@ -82,7 +87,4 @@ class TestGenerator { return !directory.empty ? "$packageNameForClass.${directoryToPackage(convertIllegalPackageChars(directory))}" : packageNameForClass } - private static String normalizePath(String path) { - return FilenameUtils.separatorsToUnix(path) - } } \ No newline at end of file diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/builder/MethodBuilder.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/builder/MethodBuilder.groovy index 55a0f66f36..2007d05b1b 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/builder/MethodBuilder.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/builder/MethodBuilder.groovy @@ -5,6 +5,7 @@ import io.codearte.accurest.config.AccurestConfigProperties import io.codearte.accurest.config.TestFramework import io.codearte.accurest.config.TestMode import io.codearte.accurest.dsl.GroovyDsl +import io.codearte.accurest.file.Contract import io.codearte.accurest.util.NamesUtil import org.codehaus.groovy.control.CompilerConfiguration @@ -17,25 +18,31 @@ class MethodBuilder { private final String methodName private final GroovyDsl stubContent private final AccurestConfigProperties configProperties + private final boolean ignored - private MethodBuilder(String methodName, GroovyDsl stubContent, AccurestConfigProperties configProperties) { + private MethodBuilder(String methodName, GroovyDsl stubContent, AccurestConfigProperties configProperties, boolean ignored) { + this.ignored = ignored this.stubContent = stubContent this.methodName = methodName this.configProperties = configProperties } - static MethodBuilder createTestMethod(File stubsFile, AccurestConfigProperties configProperties) { + static MethodBuilder createTestMethod(Contract contract, AccurestConfigProperties configProperties) { + File stubsFile = contract.path.toFile() log.debug("Stub content from file [${stubsFile.text}]") GroovyDsl stubContent = new GroovyShell(this.classLoader, new Binding(), new CompilerConfiguration(sourceEncoding:'UTF-8')).evaluate(stubsFile) log.debug("Stub content Groovy DSL [$stubContent]") String methodName = NamesUtil.camelCase(NamesUtil.toLastDot(NamesUtil.afterLast(stubsFile.path, File.separator))) - return new MethodBuilder(methodName, stubContent, configProperties) + return new MethodBuilder(methodName, stubContent, configProperties, contract.ignored) } void appendTo(BlockBuilder blockBuilder) { if (configProperties.targetFramework == TestFramework.JUNIT) { blockBuilder.addLine('@Test') } + if (ignored) { + blockBuilder.addLine('@Ignore') + } blockBuilder.addLine(configProperties.targetFramework.methodModifier + "$methodName() {") getMethodBodyBuilder().appendTo(blockBuilder) blockBuilder.addLine('}') diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/config/AccurestConfigProperties.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/config/AccurestConfigProperties.groovy index cc94fb39e9..fbf299f525 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/config/AccurestConfigProperties.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/config/AccurestConfigProperties.groovy @@ -3,14 +3,55 @@ package io.codearte.accurest.config * @author Jakub Kubrynski */ class AccurestConfigProperties { + + /** + * For which unit test library tests should be generated + */ TestFramework targetFramework = TestFramework.SPOCK + + /** + * Which mechanism should be used to invoke REST calls during tests + */ TestMode testMode = TestMode.MOCKMVC + + /** + * Base package for generated tests + */ String basePackageForTests + + /** + * Class which all generated tests should extend + */ String baseClassForTests + + /** + * Suffix for generated test classes, like Spec or Test + */ String nameSuffixForTests + + /** + * Rule class that should be added to generated tests + */ String ruleClassForTests + + /** + * Patterns that should not be taken into account for processing + */ + List excludedFiles = [] + + /** + * Patterns for which Accurest should generate @Ignored tests + */ List ignoredFiles = [] + + /** + * Imports that should be added to generated tests + */ String[] imports = [] + + /** + * Static imports that should be added to generated tests + */ String[] staticImports = [] /** diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/config/TestMode.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/config/TestMode.groovy index 1c7732d49e..5d1f339bc8 100644 --- a/accurest-core/src/main/groovy/io/codearte/accurest/config/TestMode.groovy +++ b/accurest-core/src/main/groovy/io/codearte/accurest/config/TestMode.groovy @@ -4,5 +4,18 @@ package io.codearte.accurest.config * @author Jakub Kubrynski */ enum TestMode { - MOCKMVC, EXPLICIT, JAXRSCLIENT + /** + * Uses Spring's MockMvc + */ + MOCKMVC, + + /** + * Uses direct HTTP invocations + */ + EXPLICIT, + + /** + * Uses JAX-RS client + */ + JAXRSCLIENT } \ No newline at end of file diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/file/Contract.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/file/Contract.groovy new file mode 100644 index 0000000000..6d7289b24d --- /dev/null +++ b/accurest-core/src/main/groovy/io/codearte/accurest/file/Contract.groovy @@ -0,0 +1,17 @@ +package io.codearte.accurest.file + +import java.nio.file.Path + +/** + * @author Jakub Kubrynski + */ +class Contract { + final Path path; + final boolean ignored; + + Contract(Path path, boolean ignored) { + this.path = path + this.ignored = ignored + } + +} diff --git a/accurest-core/src/main/groovy/io/codearte/accurest/file/ContractFileScanner.groovy b/accurest-core/src/main/groovy/io/codearte/accurest/file/ContractFileScanner.groovy new file mode 100644 index 0000000000..421b89a4b8 --- /dev/null +++ b/accurest-core/src/main/groovy/io/codearte/accurest/file/ContractFileScanner.groovy @@ -0,0 +1,67 @@ +package io.codearte.accurest.file + +import com.google.common.collect.ArrayListMultimap +import com.google.common.collect.Multimap +import org.apache.commons.io.FilenameUtils + +import java.nio.file.FileSystem +import java.nio.file.FileSystems +import java.nio.file.Path +import java.nio.file.PathMatcher + +/** + * @author Jakub Kubrynski + */ +class ContractFileScanner { + + private final String MATCH_PREFIX = "glob:" + private final File baseDir + private final Set excludeMatchers + private final Set ignoreMatchers + + ContractFileScanner(File baseDir, Set excluded, Set ignored) { + this.baseDir = baseDir + excludeMatchers = processPatterns(excluded, baseDir) + ignoreMatchers = processPatterns(ignored, baseDir) + } + + private Set processPatterns(Set patterns, baseDir) { + FileSystem fileSystem = FileSystems.getDefault() + return patterns.collect({ + fileSystem.getPathMatcher(MATCH_PREFIX + baseDir.toString() + File.separator + it) + }) as Set + } + + Multimap findContracts() { + Multimap result = ArrayListMultimap.create() + appendRecursively(baseDir, result) + return result + } + + private void appendRecursively(File baseDir, Multimap result) { + for (File file : baseDir.listFiles()) { + if (matchesPattern(file, excludeMatchers)) { + break; + } + if (isContractFile(file)) { + Path path = file.toPath() + result.put(file.parentFile.toPath(), new Contract(path, matchesPattern(file, ignoreMatchers))) + } else { + appendRecursively(file, result) + } + } + } + + boolean matchesPattern(File file, Set excludeMatchers) { + for (PathMatcher matcher : excludeMatchers) { + if (matcher.matches(file.toPath())) { + return true; + } + } + return false; + } + + private boolean isContractFile(File file) { + file.isFile() && FilenameUtils.getExtension(file.toString()).equalsIgnoreCase("groovy") + } +} diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/GeneratorScannerSpec.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/GeneratorScannerSpec.groovy index f11187e3d2..e95eeb1fe9 100644 --- a/accurest-core/src/test/groovy/io/codearte/accurest/GeneratorScannerSpec.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/GeneratorScannerSpec.groovy @@ -19,45 +19,17 @@ class GeneratorScannerSpec extends Specification { 6 * classGenerator.buildClass(_, _, _) >> "qwerty" } - def "should filter other directory"() { + def "should create class with full package"() { given: - File resource = new File(this.getClass().getResource("/directory/with/stubs/stubsRepositoryIndicator").toURI()) - AccurestConfigProperties properties = new AccurestConfigProperties() - properties.ignoredFiles << "**/other/**" - properties.contractsDslDir = resource.parentFile - TestGenerator testGenerator = new TestGenerator(properties, classGenerator, Stub(FileSaver)) - when: - testGenerator.generateTestClasses("com.ofg") - then: - 1 * classGenerator.buildClass(_, 'differentSpec', _) >> "qwerty" - 3 * classGenerator.buildClass(_, 'exceptionsSpec', _) >> "qwerty" - } - - def "should ignore file"() { - given: - File resource = new File(this.getClass().getResource("/directory/with/stubs/stubsRepositoryIndicator").toURI()) - AccurestConfigProperties properties = new AccurestConfigProperties() - properties.ignoredFiles << "**/other.groovy" - properties.contractsDslDir = resource.parentFile - TestGenerator testGenerator = new TestGenerator(properties, classGenerator, Stub(FileSaver)) - classGenerator.buildClass(_, _, _) >> "sample" - when: - testGenerator.generateTestClasses("com.ofg") - then: - 1 * classGenerator.buildClass({ it.size() == 1 }, 'otherSpec', _) >> "sample.groovy" - } - - def "should create class with full package"() { - given: AccurestConfigProperties properties = new AccurestConfigProperties() properties.contractsDslDir = new File(this.getClass().getResource("/directory/with/stubs/package").toURI()) TestGenerator testGenerator = new TestGenerator(properties, classGenerator, Stub(FileSaver)) - when: - testGenerator.generateTestClasses("com.ofg") - then: + when: + testGenerator.generateTestClasses("com.ofg") + then: 1 * classGenerator.buildClass(_, 'exceptionsSpec', 'com.ofg') >> "spec" 1 * classGenerator.buildClass(_, 'exceptionsSpec', 'com.ofg.v1') >> "spec1" 1 * classGenerator.buildClass(_, 'exceptionsSpec', 'com.ofg.v2') >> "spec2" - } + } } diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/MainTest.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/MainTest.groovy index 4bdc8e9cc8..dd0ab8cf10 100644 --- a/accurest-core/src/test/groovy/io/codearte/accurest/MainTest.groovy +++ b/accurest-core/src/test/groovy/io/codearte/accurest/MainTest.groovy @@ -10,7 +10,7 @@ class MainTest { contractsDslDir: new File('/home/devel/projects/codearte/accurest/accurest-core/src/test/resources/dsl'), generatedTestSourcesDir: new File('/tmp/accurest'), targetFramework: TestFramework.SPOCK, testMode: TestMode.MOCKMVC, basePackageForTests: 'io.test', - staticImports: ['com.pupablada.Test.*'], imports: ['org.innapypa.Test'], ignoredFiles: ["**/other"]) + staticImports: ['com.pupablada.Test.*'], imports: ['org.innapypa.Test'], excludedFiles: ["**/other"]) println new TestGenerator(properties).generate() } } diff --git a/accurest-core/src/test/groovy/io/codearte/accurest/file/ContractFileScannerSpec.groovy b/accurest-core/src/test/groovy/io/codearte/accurest/file/ContractFileScannerSpec.groovy new file mode 100644 index 0000000000..d6c0fdd1d2 --- /dev/null +++ b/accurest-core/src/test/groovy/io/codearte/accurest/file/ContractFileScannerSpec.groovy @@ -0,0 +1,30 @@ +package io.codearte.accurest.file + +import com.google.common.collect.Multimap +import spock.lang.Specification + +import java.nio.file.Path + +/** + * @author Jakub Kubrynski + */ +class ContractFileScannerSpec extends Specification { + + def "should find contract files"() { + given: + File baseDir = new File(this.getClass().getResource("/directory/with/stubs").toURI()) + Set excluded = ["package/**"] as Set + Set ignored = ["other/different/**"] as Set + ContractFileScanner scanner = new ContractFileScanner(baseDir, excluded, ignored) + when: + Multimap result = scanner.findContracts() + then: + result.keySet().size() == 3 + result.get(baseDir.toPath().resolve("different")).size() == 1 + result.get(baseDir.toPath().resolve("other")).size() == 2 + and: + Collection ignoredSet = result.get(baseDir.toPath().resolve("other").resolve("different")) + ignoredSet.size() == 1 + ignoredSet.ignored == [true] + } +}