Merge pull request #25 from 4finance/topic/22-pluginConverters

Topic/22 plugin converters
This commit is contained in:
Jakub Kubryński
2015-02-21 12:23:17 +01:00
25 changed files with 422 additions and 62 deletions

View File

@@ -0,0 +1,12 @@
package io.codearte.accurest.wiremock
import groovy.transform.CompileStatic
import io.coderate.accurest.AccurestException
@CompileStatic
class ConversionAccurestException extends AccurestException {
ConversionAccurestException(String message, Throwable cause) {
super(message, cause)
}
}

View File

@@ -0,0 +1,13 @@
package io.codearte.accurest.wiremock
import groovy.transform.CompileStatic
import io.coderate.accurest.dsl.WiremockStubStrategy
@CompileStatic
class DslToWiremockClientConverter extends DslToWiremockConverter {
@Override
String convertContent(String dslBody) {
return new WiremockStubStrategy(createGroovyDSLfromStringContent(dslBody)).toWiremockClientStub()
}
}

View File

@@ -0,0 +1,22 @@
package io.codearte.accurest.wiremock
import groovy.transform.CompileStatic
import io.coderate.accurest.dsl.GroovyDsl
@CompileStatic
abstract class DslToWiremockConverter implements SingleFileConverter {
@Override
boolean canHandleFileName(String fileName) {
return fileName.endsWith('.groovy')
}
@Override
String generateOutputFileNameForInput(String inputFileName) {
return inputFileName.replaceAll('.groovy', '.json')
}
protected GroovyDsl createGroovyDSLfromStringContent(String groovyDslAsString) {
return (GroovyDsl)new GroovyShell(this.class.classLoader).evaluate("$groovyDslAsString")
}
}

View File

@@ -0,0 +1,13 @@
package io.codearte.accurest.wiremock
import groovy.transform.CompileStatic
import io.coderate.accurest.dsl.WiremockStubStrategy
@CompileStatic
class DslToWiremockServerConverter extends DslToWiremockConverter {
@Override
String convertContent(String dslBody) {
return new WiremockStubStrategy(createGroovyDSLfromStringContent(dslBody)).toWiremockServerStub()
}
}

View File

@@ -0,0 +1,53 @@
package io.codearte.accurest.wiremock
import groovy.io.FileType
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
@Slf4j
@CompileStatic
class RecursiveFilesConverter {
private final SingleFileConverter singleFileConverter
private final File sourceRootDirectory
private final File targetRootDirectory
RecursiveFilesConverter(SingleFileConverter singleFileConverter, File sourceRootDirectory, File targetRootDirectory) {
this.singleFileConverter = singleFileConverter
this.sourceRootDirectory = sourceRootDirectory
this.targetRootDirectory = targetRootDirectory
}
void processFiles() {
sourceRootDirectory.eachFileRecurse(FileType.FILES) { File sourceFile ->
try {
if (!singleFileConverter.canHandleFileName(sourceFile.name)) {
return
}
String convertedContent = singleFileConverter.convertContent(sourceFile.text)
Path absoluteTargetPath = createAndReturnTargetDirectory(sourceFile)
File newGroovyFile = createTargetFileWithProperName(absoluteTargetPath, sourceFile)
newGroovyFile.text = convertedContent
} catch (Exception e) {
throw new ConversionAccurestException("Unable to convertion of ${sourceFile.name}", e)
}
}
}
private Path createAndReturnTargetDirectory(File sourceFile) {
Path relativePath = Paths.get(sourceRootDirectory.toURI()).relativize(sourceFile.parentFile.toPath())
Path absoluteTargetPath = targetRootDirectory.toPath().resolve(relativePath)
Files.createDirectories(absoluteTargetPath)
absoluteTargetPath
}
private File createTargetFileWithProperName(Path absoluteTargetPath, File sourceFile) {
File newGroovyFile = new File(absoluteTargetPath.toFile(), singleFileConverter.generateOutputFileNameForInput(sourceFile.name))
log.info("Creating new json [$newGroovyFile.path]")
newGroovyFile
}
}

View File

@@ -0,0 +1,13 @@
package io.codearte.accurest.wiremock
import groovy.transform.CompileStatic
@CompileStatic
interface SingleFileConverter {
boolean canHandleFileName(String fileName)
String convertContent(String content)
String generateOutputFileNameForInput(String inputFileName)
}

View File

@@ -1,4 +1,5 @@
package io.codearte.accurest.wiremock
import groovy.io.FileType
import groovy.json.JsonOutput
import groovy.json.JsonSlurper

View File

@@ -0,0 +1,29 @@
package io.codearte.accurest.wiremock
import groovy.json.JsonSlurper
import spock.lang.Specification
class DslToWiremockClientConverterSpec extends Specification {
def "should convert DSL file to Wiremock JSON"() {
given:
def converter = new DslToWiremockClientConverter()
and:
String dslBody = """
io.coderate.accurest.dsl.GroovyDsl.make {
request {
method('PUT')
urlPattern \$(client('/[0-9]{2}'), server('/12'))
}
response {
status 200
}
}
"""
when:
String json = converter.convertContent(dslBody)
then:
new JsonSlurper().parseText(json) == new JsonSlurper().parseText("""
{"request":{"method":"PUT","urlPattern":"/[0-9]{2}"},"response":{"status":200}}""")
}
}

View File

@@ -0,0 +1,29 @@
package io.codearte.accurest.wiremock
import groovy.json.JsonSlurper
import spock.lang.Specification
class DslToWiremockServerConverterSpec extends Specification {
def "should convert DSL file to Wiremock JSON"() {
given:
def converter = new DslToWiremockServerConverter()
and:
String dslBody = """
io.coderate.accurest.dsl.GroovyDsl.make {
request {
method('PUT')
urlPattern \$(client('/[0-9]{2}'), server('/12'))
}
response {
status 200
}
}
"""
when:
String json = converter.convertContent(dslBody)
then:
new JsonSlurper().parseText(json) == new JsonSlurper().parseText("""
{"request":{"method":"PUT","urlPattern":"/12"},"response":{"status":200}}""")
}
}

View File

@@ -0,0 +1,65 @@
package io.codearte.accurest.wiremock
import org.apache.commons.io.FileUtils
import org.apache.commons.io.filefilter.TrueFileFilter
import org.junit.Rule
import org.junit.rules.TemporaryFolder
import spock.lang.Specification
import java.nio.file.Path
import java.nio.file.Paths
class RecursiveFilesConverterSpec extends Specification {
private static final Set<String> EXPECTED_TARGET_FILES = ["dslRoot.json", "dir1/dsl1.json", "dir1/dsl1b.json", "dir2/dsl2.json"]
@Rule
public TemporaryFolder tmpFolder = new TemporaryFolder();
def "should recursively convert all matching files"() {
given:
File originalSourceRootDirectory = new File(this.getClass().getResource("/converter/source").toURI())
File sourceRootDirectory = tmpFolder.newFolder("source")
File targetRootDirectory = tmpFolder.newFolder("target")
FileUtils.copyDirectory(originalSourceRootDirectory, sourceRootDirectory)
and:
def singleFileConverterStub = Stub(SingleFileConverter)
singleFileConverterStub.canHandleFileName(_) >> { String fileName -> fileName.endsWith(".groovy") }
singleFileConverterStub.convertContent(_) >> { "converted" }
singleFileConverterStub.generateOutputFileNameForInput(_) >> { String inputFileName -> inputFileName.replaceAll('.groovy', '.json') }
RecursiveFilesConverter recursiveFilesConverter = new RecursiveFilesConverter(singleFileConverterStub, sourceRootDirectory, targetRootDirectory)
when:
recursiveFilesConverter.processFiles()
then:
Collection<File> createdFiles = FileUtils.listFiles(targetRootDirectory, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE)
Set<String> relativizedCreatedFiles = getRelativePathsForFilesInDirectory(createdFiles, targetRootDirectory)
EXPECTED_TARGET_FILES == relativizedCreatedFiles
and:
createdFiles.each { it.text == "converted" }
}
def "on failure should break processing and throw meaningful exception"() {
given:
def sourceFile = tmpFolder.newFile()
and:
def singleFileConverterStub = Stub(SingleFileConverter)
singleFileConverterStub.canHandleFileName(_) >> { true }
singleFileConverterStub.convertContent(_) >> { throw new NullPointerException("Test conversion error") }
singleFileConverterStub.generateOutputFileNameForInput(_) >> { String inputFileName -> "${inputFileName}2" }
RecursiveFilesConverter recursiveFilesConverter = new RecursiveFilesConverter(singleFileConverterStub, tmpFolder.root, tmpFolder.root)
when:
recursiveFilesConverter.processFiles()
then:
def e = thrown(ConversionAccurestException)
e.message?.contains(sourceFile.name)
e.cause?.message == "Test conversion error"
}
private static Set<String> getRelativePathsForFilesInDirectory(Collection<File> createdFiles, File targetRootDirectory) {
Path rootSourcePath = Paths.get(targetRootDirectory.toURI())
Set<String> relativizedCreatedFiles = createdFiles.collect { File file ->
rootSourcePath.relativize(Paths.get(file.toURI())).toString()
}
relativizedCreatedFiles
}
}

View File

@@ -0,0 +1,12 @@
io.coderate.accurest.dsl.GroovyDsl.make {
request {
method('PUT')
headers {
header 'Content-Type': 'application/json'
}
urlPattern $(client('/[0-9]{2}'), server('/12'))
}
response {
status 200
}
}

View File

@@ -0,0 +1,12 @@
io.coderate.accurest.dsl.GroovyDsl.make {
request {
method('PUT')
headers {
header 'Content-Type': 'application/json'
}
urlPattern $(client('/[0-9]{2}'), server('/12'))
}
response {
status 200
}
}

View File

@@ -0,0 +1,12 @@
io.coderate.accurest.dsl.GroovyDsl.make {
request {
method('PUT')
headers {
header 'Content-Type': 'application/json'
}
urlPattern $(client('/[0-9]{2}'), server('/12'))
}
response {
status 200
}
}

View File

@@ -0,0 +1,12 @@
io.coderate.accurest.dsl.GroovyDsl.make {
request {
method('PUT')
headers {
header 'Content-Type': 'application/json'
}
urlPattern $(client('/[0-9]{2}'), server('/12'))
}
response {
status 200
}
}

View File

@@ -5,8 +5,11 @@ package io.coderate.accurest
*/
class AccurestException extends RuntimeException {
def AccurestException(String message) {
AccurestException(String message) {
super(message)
}
AccurestException(String message, Throwable cause) {
super(message, cause)
}
}

View File

@@ -13,18 +13,17 @@ import static io.coderate.accurest.util.NamesUtil.capitalize
@CompileStatic
class FileSaver {
String targetDirectory
File targetDirectory
TestFramework framework
FileSaver(String targetDirectory, TestFramework framework) {
FileSaver(File targetDirectory, TestFramework framework) {
this.targetDirectory = targetDirectory
this.framework = framework
}
void saveClassFile(String fileName, String packageName, byte[] classBytes) {
def testBaseDir = Paths.get(targetDirectory, NamesUtil.packageToDirectory(packageName))
def testBaseDir = Paths.get(targetDirectory.absolutePath, NamesUtil.packageToDirectory(packageName))
Files.createDirectories(testBaseDir)
def classPath = Paths.get(testBaseDir.toString(), capitalize(fileName) + getTestClassSuffix() + getTestClassExtension())
.toAbsolutePath()

View File

@@ -14,23 +14,21 @@ import static io.coderate.accurest.util.NamesUtil.afterLast
class TestGenerator {
private final AccurestConfigProperties configProperties
private final String stubsBaseDirectory
private AtomicInteger counter = new AtomicInteger()
private SingleTestGenerator generator
private FileSaver saver
private DirectoryScanner directoryScanner
TestGenerator(AccurestConfigProperties accurestConfigProperties) {
this(accurestConfigProperties, new SingleTestGenerator(accurestConfigProperties), new FileSaver(accurestConfigProperties.generatedTestSourcesDir, accurestConfigProperties.targetFramework))
this(accurestConfigProperties, new SingleTestGenerator(accurestConfigProperties),
new FileSaver(accurestConfigProperties.generatedTestSourcesDir, accurestConfigProperties.targetFramework))
}
TestGenerator(AccurestConfigProperties configProperties, SingleTestGenerator generator, FileSaver saver) {
this.configProperties = configProperties
File stubsResource = new File(configProperties.stubsBaseDirectory)
if (stubsResource == null) {
if (configProperties.stubsBaseDirectory == null) {
throw new AccurestException("Stubs directory not found under " + configProperties.stubsBaseDirectory)
}
this.stubsBaseDirectory = stubsResource.path
this.generator = generator
this.saver = saver
this.directoryScanner = new DirectoryScanner()
@@ -61,7 +59,7 @@ class TestGenerator {
return includedFile.matches(includedDirectoryRelativePath + File.separator + "[A-Za-z0-9]*\\.json")
}
.collect {
return new File(stubsBaseDirectory + File.separator + it)
return new File(configProperties.stubsBaseDirectory, it)
}
if (filesToClass.size()) {
def className = afterLast(includedDirectoryRelativePath, File.separator)
@@ -71,6 +69,4 @@ class TestGenerator {
}
}
}
}

View File

@@ -6,12 +6,14 @@ package io.coderate.accurest.config
class AccurestConfigProperties {
TestFramework targetFramework = TestFramework.SPOCK
TestMode testMode = TestMode.MOCKMVC
String stubsBaseDirectory = 'src/test/resources/stubs'
String basePackageForTests = 'io.codearte.accurest.tests'
File stubsBaseDirectory
String basePackageForTests
String baseClassForTests
String ruleClassForTests
List<String> ignoredFiles = []
String generatedTestSourcesDir = 'build/generated-sources/accurest'
File generatedTestSourcesDir
String[] imports = []
String[] staticImports = []
File groovyDslDir
File generatedWiremockClientStubsDir
}

View File

@@ -15,12 +15,12 @@ class WiremockStubStrategy {
}
String toWiremockClientStub() {
return JsonOutput.toJson([request: wiremockRequestStubStrategy.buildClientRequestContent(),
response: wiremockResponseStubStrategy.buildClientResponseContent()])
return JsonOutput.prettyPrint(JsonOutput.toJson([request: wiremockRequestStubStrategy.buildClientRequestContent(),
response: wiremockResponseStubStrategy.buildClientResponseContent()]))
}
String toWiremockServerStub() {
return JsonOutput.toJson([request: wiremockRequestStubStrategy.buildServerRequestContent(),
response: wiremockResponseStubStrategy.buildServerResponseContent()])
return JsonOutput.prettyPrint(JsonOutput.toJson([request: wiremockRequestStubStrategy.buildServerRequestContent(),
response: wiremockResponseStubStrategy.buildServerResponseContent()]))
}
}

View File

@@ -55,8 +55,8 @@ class Body {
} else if(bodyAsList) {
bodyAsList.collect { it.clientValue }
}
return body.collectEntries {
Map.Entry<String, DslProperty> entry -> [(entry.key) : entry.value.clientValue]
return body.collectEntries { Map.Entry<String, DslProperty> entry ->
[(entry.key) : entry.value.clientValue]
} as Map<String, Object>
}
@@ -66,8 +66,8 @@ class Body {
} else if(bodyAsList) {
bodyAsList.collect { it.serverValue }
}
return body.collectEntries {
Map.Entry<String, DslProperty> entry -> [(entry.key) : entry.value.serverValue]
return body.collectEntries { Map.Entry<String, DslProperty> entry ->
[(entry.key) : entry.value.serverValue]
} as Map<String, Object>
}
}

View File

@@ -14,7 +14,7 @@ class TestGeneratorSpec extends Specification {
given:
File resource = new File(this.getClass().getResource("/directory/with/stubs/stubsRepositoryIndicator").toURI())
AccurestConfigProperties properties = new AccurestConfigProperties()
properties.stubsBaseDirectory = resource.parentFile.path
properties.stubsBaseDirectory = resource.parentFile
TestGenerator testGenerator = new TestGenerator(properties, classGenerator, Stub(FileSaver))
when:
testGenerator.generateTestClasses("com.ofg")
@@ -27,7 +27,7 @@ class TestGeneratorSpec extends Specification {
File resource = new File(this.getClass().getResource("/directory/with/stubs/stubsRepositoryIndicator").toURI())
AccurestConfigProperties properties = new AccurestConfigProperties()
properties.ignoredFiles << "**/other/**"
properties.stubsBaseDirectory = resource.parentFile.path
properties.stubsBaseDirectory = resource.parentFile
TestGenerator testGenerator = new TestGenerator(properties, classGenerator, Stub(FileSaver))
when:
testGenerator.generateTestClasses("com.ofg")
@@ -40,7 +40,7 @@ class TestGeneratorSpec extends Specification {
File resource = new File(this.getClass().getResource("/directory/with/stubs/stubsRepositoryIndicator").toURI())
AccurestConfigProperties properties = new AccurestConfigProperties()
properties.ignoredFiles << "**/other.json"
properties.stubsBaseDirectory = resource.parentFile.path
properties.stubsBaseDirectory = resource.parentFile
TestGenerator testGenerator = new TestGenerator(properties, classGenerator, Stub(FileSaver))
classGenerator.buildClass(_, _, _) >> "sample"
when:

View File

@@ -1,68 +1,68 @@
package io.codearte.accurest.plugin
import io.coderate.accurest.AccurestException
import io.coderate.accurest.TestGenerator
import io.coderate.accurest.config.AccurestConfigProperties
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.Task
/**
* @author Jakub Kubrynski
*/
class AccurestGradlePlugin implements Plugin<Project> {
private static final String TASK_NAME = 'generateAccurest'
private static final String GENERATE_SERVER_TESTS_TASK_NAME = 'generateAccurest'
private static final String DSL_TO_WIREMOCK_CLIENT_TASK_NAME = 'generateWiremockClientStubs'
private static final Class IDEA_PLUGIN_CLASS = org.gradle.plugins.ide.idea.IdeaPlugin
private Project project
@Override
void apply(Project project) {
this.project = project
AccurestConfigProperties extension = project.extensions.create('accurest', AccurestConfigProperties)
project.compileTestGroovy.dependsOn(TASK_NAME)
project.compileTestGroovy.dependsOn(GENERATE_SERVER_TESTS_TASK_NAME)
project.task(TASK_NAME) << {
project.logger.info("Accurest Plugin: Invoking test sources generation")
extension.stubsBaseDirectory = project.projectDir.path + File.separator + extension.stubsBaseDirectory
project.sourceSets.test.groovy {
project.logger.info("Registering $extension.generatedTestSourcesDir as test source directory")
srcDir extension.generatedTestSourcesDir
}
extension.generatedTestSourcesDir = buildGeneratedSourcesDir(project, extension)
try {
TestGenerator generator = new TestGenerator(extension)
int generatedClasses = generator.generate()
project.logger.info("Generated {} test classes", generatedClasses)
} catch (AccurestException e) {
project.logger.error("Accurest Plugin exception: {}", e.getMessage())
}
}
setConfigurationDefaults(extension)
createGenerateTestsTask(extension)
createAndConfigureGenerateWiremockClientStubsFromDslTask(extension)
project.afterEvaluate {
def hasIdea = project.plugins.findPlugin(IDEA_PLUGIN_CLASS)
if (hasIdea) {
project.idea {
module {
testSourceDirs += new File(buildGeneratedSourcesDir(project, extension))
testSourceDirs += extension.generatedTestSourcesDir
}
}
}
}
}
private String buildGeneratedSourcesDir(Project project, def extension) {
String moduleDir
if (project.getParent()) {
moduleDir = project.name + File.separator + extension.generatedTestSourcesDir
} else {
moduleDir = extension.generatedTestSourcesDir
void setConfigurationDefaults(AccurestConfigProperties extension) {
extension.with {
generatedTestSourcesDir = project.file("${project.buildDir}/generated-sources/accurest")
stubsBaseDirectory = project.file("${project.rootDir}/src/test/resources/stubs") //TODO: Use sourceset
basePackageForTests = 'io.codearte.accurest.tests'
}
moduleDir
}
private void createGenerateTestsTask(AccurestConfigProperties extension) {
Task task = project.tasks.create(GENERATE_SERVER_TESTS_TASK_NAME, GenerateServerTestsTask)
task.description = "Generate server tests from GroovyDSL"
task.conventionMapping.with {
generatedTestSourcesDir = { extension.generatedTestSourcesDir }
configProperties = { extension }
}
}
private void createAndConfigureGenerateWiremockClientStubsFromDslTask(AccurestConfigProperties extension) {
Task task = project.tasks.create(DSL_TO_WIREMOCK_CLIENT_TASK_NAME, GenerateWiremockClientStubsFromDslTask)
task.description = "Generate Wiremock client stubs from GroovyDSL"
task.conventionMapping.with {
groovyDslDir = { extension.groovyDslDir }
generatedWiremockClientStubsDir = { extension.generatedWiremockClientStubsDir }
}
}
}

View File

@@ -0,0 +1,36 @@
package io.codearte.accurest.plugin
import io.coderate.accurest.AccurestException
import io.coderate.accurest.TestGenerator
import io.coderate.accurest.config.AccurestConfigProperties
import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.TaskAction
class GenerateServerTestsTask extends DefaultTask {
@OutputDirectory File generatedTestSourcesDir
//TODO: How to deal with @Input*, @Output* and that domain object?
AccurestConfigProperties configProperties
@TaskAction
void generate() {
project.logger.info("Accurest Plugin: Invoking test sources generation")
project.sourceSets.test.groovy {
project.logger.info("Registering ${getConfigProperties().generatedTestSourcesDir} as test source directory")
srcDir getConfigProperties().generatedTestSourcesDir
}
try {
//TODO: What with that? How to pass?
TestGenerator generator = new TestGenerator(getConfigProperties())
int generatedClasses = generator.generate()
project.logger.info("Generated {} test classes", generatedClasses)
} catch (AccurestException e) {
throw new GradleException("Accurest Plugin exception: ${e.message}", e)
}
}
}

View File

@@ -0,0 +1,24 @@
package io.codearte.accurest.plugin
import io.codearte.accurest.wiremock.DslToWiremockClientConverter
import io.codearte.accurest.wiremock.RecursiveFilesConverter
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.InputDirectory
import org.gradle.api.tasks.TaskAction
//TODO: Implement as an incremental task: https://gradle.org/docs/current/userguide/custom_tasks.html#incremental_tasks ?
class GenerateWiremockClientStubsFromDslTask extends DefaultTask {
@InputDirectory File groovyDslDir
@InputDirectory File generatedWiremockClientStubsDir
@TaskAction
void generate() {
project.logger.info("Accurest Plugin: Invoking GroovyDSL to Wiremock client stubs conversion")
project.logger.debug("From '${getGroovyDslDir()}' to '${getGeneratedWiremockClientStubsDir()}'")
RecursiveFilesConverter converter = new RecursiveFilesConverter(new DslToWiremockClientConverter(), getGroovyDslDir(),
getGeneratedWiremockClientStubsDir())
converter.processFiles()
}
}

View File

@@ -1,5 +1,5 @@
ext {
projectVersion = '0.3.0-SNAPSHOT'
projectVersion = '0.3.1-SNAPSHOT'
projectGroup = 'io.codearte.accurest'
}
@@ -47,10 +47,10 @@ subprojects {
project(':accurest-core') {
dependencies {
compile 'org.slf4j:slf4j-api:[1.6.0,)'
compile 'org.codehaus.plexus:plexus-utils:3.0.21'
testCompile 'cglib:cglib-nodep:2.2'
testCompile 'org.objenesis:objenesis:2.1'
}
}
@@ -58,12 +58,14 @@ project(':accurest-converters') {
dependencies {
compile project(':accurest-core')
compile 'org.apache.commons:commons-lang3:3.3.2'
compile 'commons-io:commons-io:[2.4,)'
}
}
project(':accurest-gradle-plugin') {
dependencies {
compile project(':accurest-core')
compile project(':accurest-converters')
compile gradleApi()
}
}