Implement new way of scanning for contract files. Add support for generating ignored tests. Fix #28 #53
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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('}')
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user