Implement new way of scanning for contract files. Add support for generating ignored tests. Fix #28 #53

This commit is contained in:
Jakub Kubrynski
2016-02-07 01:43:05 +01:00
parent 347d46a7f2
commit da5313e376
11 changed files with 226 additions and 74 deletions

View File

@@ -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

View File

@@ -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<File> listOfFiles, String className, String classPackage) {
String buildClass(Collection<Contract> 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)

View File

@@ -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<Path, Contract> contracts = contractFileScanner.findContracts()
contracts.asMap().entrySet().each {
Map.Entry<Path, Collection<Contract>> entry -> processIncludedDirectory(relativizeContractPath(entry), entry.getValue(), basePackageName)
}
}
private String relativizeContractPath(Map.Entry<Path, Collection<Path>> 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<File> 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<Contract> 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)
}
}

View File

@@ -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('}')

View File

@@ -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<String> excludedFiles = []
/**
* Patterns for which Accurest should generate @Ignored tests
*/
List<String> ignoredFiles = []
/**
* Imports that should be added to generated tests
*/
String[] imports = []
/**
* Static imports that should be added to generated tests
*/
String[] staticImports = []
/**

View File

@@ -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
}

View File

@@ -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
}
}

View File

@@ -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<PathMatcher> excludeMatchers
private final Set<PathMatcher> ignoreMatchers
ContractFileScanner(File baseDir, Set<String> excluded, Set<String> ignored) {
this.baseDir = baseDir
excludeMatchers = processPatterns(excluded, baseDir)
ignoreMatchers = processPatterns(ignored, baseDir)
}
private Set<PathMatcher> processPatterns(Set<String> patterns, baseDir) {
FileSystem fileSystem = FileSystems.getDefault()
return patterns.collect({
fileSystem.getPathMatcher(MATCH_PREFIX + baseDir.toString() + File.separator + it)
}) as Set
}
Multimap<Path, Contract> findContracts() {
Multimap<Path, Contract> result = ArrayListMultimap.create()
appendRecursively(baseDir, result)
return result
}
private void appendRecursively(File baseDir, Multimap<Path, Contract> 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<PathMatcher> 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")
}
}

View File

@@ -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"
}
}
}

View File

@@ -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()
}
}

View File

@@ -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<String> excluded = ["package/**"] as Set
Set<String> ignored = ["other/different/**"] as Set
ContractFileScanner scanner = new ContractFileScanner(baseDir, excluded, ignored)
when:
Multimap<Path, Contract> 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<Contract> ignoredSet = result.get(baseDir.toPath().resolve("other").resolve("different"))
ignoredSet.size() == 1
ignoredSet.ignored == [true]
}
}