为什么在执行JUnit测试时Gradle找不到资源?

时间:2018-07-24 04:55:23

标签: gradle npm build.gradle npm-scripts

我目前正在尝试将项目从Gradle 3.5迁移到最新的Gradle版本4.9,并且遇到了问题,即Gradle找不到执行JUnit测试所需的资源。这些资源是预先生成的,并且包含一些配置文件和参考数据。

该项目使用npm和Gradle的组合进行编译。通常,我们运行一个npm脚本,该脚本使用run-s依次执行几个任务,例如webpack构建以生成一些静态资源。最后,我们使用Gradle将所有内容一起编译到一个jar中。最后的Gradle任务还执行junit测试。此步骤失败,并使整个构建失败。 Gradle赛跑者抱怨找不到一些资源,尽管将它们正确地复制到了正确的位置。

当我在最后一个Gradle构建步骤失败之后立即通过调用./gradlew test --rerun-tasks执行Gradle包装器时,它将成功执行JUnit测试。

在使用Gradle 3.5时,它就像一个吊饰。我知道,Gradle 4.x中的目录结构已更改,但这似乎不是问题。

我的怀疑是,这与我们通常运行整个构建过程的方式有关。与npm相结合。

该项目使用了node.js 8.11.3随附的npm verison 5.6.0。项目中有两个package.json文件,一个在项目的根级别,一个在子目录中。这是package.json在根级别上的样子(我删除了一些相关的部分,例如存储库URL等):

{
  "name": "foobar",
  "version": "1.0.0",
  "scripts": {
    "reloaded:production:build": "cd src/main/webapp-reloaded && npm run production:build",
    "prod:build": "run-s clean:build gradle:translate build:assets prod:build:js reloaded:production:build && ./gradlew --refresh-dependencies --info --warning-mode all prod",
    "prod:build:js": "run-s lint prod:build:js:client",
    "prod:build:js:client": "webpack --progress --config config/webpack/production.config.js",
    "build:assets": "run-s clean:generated generate:assets",
    "generate:assets": "run-p generate:soyTemplates generate:icons generate:reactIcons generate:translations",
    "generate:icons": "node config/tasks/generateIcons.js",
    "generate:reactIcons": "mde-react-icons --files node_modules/mde-icons/svgmin/*.svg --dest src/main/webapp/generated/react-icons --color",
    "generate:soyTemplates": "node config/tasks/compileSoy.js",
    "gradle:translate": "./gradlew --refresh-dependencies --info --warning-mode all translate",
    "clean:build": "rimraf build",
    "clean:generated": "rimraf src/main/webapp/generated",
    "lint": "eslint src/main/webapp/js/**/*.js"
  },
  "repository": {
    "type": "git",
    "url": "..."
  },
  "contributors": [
    "See README.md"
  ],
  "license": "UNLICENSED",
  "private": true,
  "dependencies": {
    "autobind-decorator": "^1.4.0",
    "axios": "^0.15.3",
    "babel-polyfill": "^6.23.0",
    "classnames": "^2.2.5",
    "google-maps": "~3.2.1",
    "intl": "^1.2.5",
    "jquery": "^2.2.4",
    "jquery-cycle2": "^2.1.6",
    "jquery-lazyload": "^1.9.4",
    "lodash": "^4.17.4",
    "prop-types": "^15.6.0",
    "raf": "^3.4.0",
    "react": "^16.2.0",
    "react-autocomplete": "^1.7.2",
    "react-dom": "^16.2.0",
    "react-intl": "^2.3.0",
    "react-modal": "^3.1.10",
    "react-redux": "^5.0.3",
    "redux": "^3.6.0",
    "redux-debounced": "^0.4.0",
    "redux-form": "^7.2.1",
    "redux-thunk": "^2.2.0",
    "slick-carousel": "~1.6.0",
    "whatwg-fetch": "^2.0.3"
  },
  "devDependencies": {
    "async": "^2.1.5",
    "babel-core": "^6.23.1",
    "babel-eslint": "^7.2.3",
    "babel-loader": "^6.3.2",
    "babel-plugin-lodash": "^3.2.11",
    "babel-plugin-transform-decorators-legacy": "^1.3.4",
    "babel-plugin-transform-object-rest-spread": "^6.23.0",
    "babel-preset-latest": "^6.22.0",
    "babel-preset-react": "^6.23.0",
    "babel-preset-react-optimize": "^1.0.1",
    "copy": "^0.3.0",
    "css-loader": "^0.28.9",
    "eslint": "^3.16.1",
    "eslint-plugin-react": "^6.10.0",
    "expose-loader": "^0.7.3",
    "extract-text-webpack-plugin": "^2.1.0",
    "file-loader": "^0.11.2",
    "less": "^2.7.2",
    "less-loader": "^4.0.0",
    "less-plugin-autoprefix": "~1.5.1",
    "less-plugin-clean-css": "~1.5.1",
    "npm-run-all": "^4.0.2",
    "postcss-cssnext": "^3.1.0",
    "postcss-import": "^11.0.0",
    "postcss-loader": "^2.0.10",
    "properties-reader": "0.0.15",
    "react-hot-loader": "^3.0.0-beta.6",
    "rimraf": "^2.6.1",
    "stmux": "^1.5.4",
    "style-loader": "^0.19.1",
    "webpack": "^2.6.1",
    "webpack-dashboard": "^0.3.0",
    "webpack-dev-server": "^2.11.1",
    "webpack-manifest-plugin": "~1.3.1",
    "webpack-merge": "^4.1.0"
  },
  "optionalDependencies": {
    "ttab": "^0.6.1"
  }
}

子目录中的package.json如下所示:

{
  "name": "foobar-reloaded",
  "version": "0.0.1",
  "main": "index.js",
  "scripts": {
    "tab:js:client": "ttab -t 'Client' npm run dev:build:js:client",
    "tab:js:server": "ttab -t '⚛React Renderer' npm run dev:build:js:server",
    "tab:server": "ttab -t '☕Java Server' 'cd ../../..; ./gradlew run --no-daemon'",
    "production:build": "run-s test:coverage jenkins:flow production:build:js",
    "production:build:js": "run-p production:build:js:client production:build:js:server",
    "production:build:js:client": "cross-env BUILD_ENV=production NODE_ENV=production webpack --color --config config/webpack/client.config.js",
    "production:build:js:server": "cross-env BUILD_ENV=production NODE_ENV=production webpack --config config/webpack/server.config.js",
    "flow": "flow",
    "jenkins:flow": "flow check",
    "lint": "eslint --fix src config e2e/src",
    "test": "cross-env NODE_ENV=testing jest",
    "test:coverage": "cross-env NODE_ENV=testing jest --coverage",
    "test:watch": "cross-env NODE_ENV=testing jest --watch",
    "format": "prettier --config .prettierrc --write \"src/**/*.js\" \"config/**/*.js\" \"src/**/*.css\"",
    "precommit": "lint-staged",
    "prepush": "eslint src config && npm run test:coverage && flow",
  },
  "repository": {
    "type": "git",
  },
  "lint-staged": {
    "*.js": [
      "prettier --config .prettierrc --write",
      "eslint --fix",
      "git add"
    ],
    "*.css": [
      "prettier --config .prettierrc --write",
      "git add"
    ]
  },
  "dependencies": {
    "classnames": "^2.2.6",
    "file-loader": "^1.1.11",
    "history": "^4.7.2",
    "intl": "^1.2.5",
    "isomorphic-fetch": "^2.2.1",
    "lodash": "^4.17.5",
    "prop-types": "^15.6.1",
    "query-string": "^5.1.1",
    "react": "16.2.0",
    "react-dom": "16.2.0",
    "react-helmet": "^5.2.0",
    "react-intl": "^2.4.0",
    "react-redux": "^5.0.7",
    "react-router-dom": "^4.3.1",
    "react-router-scroll-4": "^1.0.0-beta.1",
    "redux": "^3.7.2",
    "redux-thunk": "^2.3.0",
    "serialize-javascript": "^1.4.0",
    "testcafe-reporter-html": "^1.1.3",
    "trackjs": "^2.10.2",
    "url": "^0.11.0"
  },
  "devDependencies": {
    "babel-cli": "^6.26.0",
    "babel-core": "^6.26.0",
    "babel-eslint": "^8.2.2",
    "babel-jest": "^22.4.3",
    "babel-loader": "^7.1.4",
    "babel-plugin-lodash": "^3.3.4",
    "babel-plugin-syntax-dynamic-import": "^6.18.0",
    "babel-plugin-transform-decorators-legacy": "^1.3.5",
    "babel-plugin-transform-object-rest-spread": "^6.26.0",
    "babel-plugin-transform-regenerator": "^6.26.0",
    "babel-plugin-transform-runtime": "^6.23.0",
    "babel-polyfill": "^6.26.0",
    "babel-preset-env": "^1.6.1",
    "babel-preset-flow": "^6.23.0",
    "babel-preset-react": "^6.24.1",
    "cross-env": "^5.2.0",
    "css-loader": "^0.28.11",
    "enzyme": "^3.2.0",
    "enzyme-adapter-react-16": "^1.1.1",
    "enzyme-to-json": "^3.3.3",
    "eslint": "^4.19.1",
    "eslint-config-prettier": "^2.9.0",
    "eslint-config-standard": "^11.0.0",
    "eslint-plugin-flowtype": "^2.49.3",
    "eslint-plugin-import": "^2.10.0",
    "eslint-plugin-node": "^6.0.1",
    "eslint-plugin-promise": "^3.8.0",
    "eslint-plugin-react": "^7.9.1",
    "eslint-plugin-standard": "^3.0.1",
    "extract-text-webpack-plugin": "^3.0.2",
    "flow-bin": "^0.73.0",
    "husky": "^0.14.3",
    "npm-delay": "^1.0.4",
    "identity-obj-proxy": "^3.0.0",
    "isomorphic-style-loader": "^4.0.0",
    "jest": "^22.4.3",
    "lint-staged": "^7.2.0",
    "npm-run-all": "^4.1.2",
    "postcss-cssnext": "^3.0.2",
    "postcss-import": "^11.0.0",
    "postcss-loader": "^2.1.3",
    "prettier": "1.10.2",
    "raw-loader": "^0.5.1",
    "react-hot-loader": "^3.1.3",
    "stmux": "^1.5.4",
    "storybook-readme": "^3.3.0",
    "style-loader": "^0.20.3",
    "testcafe": "^0.19.1",
    "testcafe-react-selectors": "^2.1.0",
    "webpack": "^3.11.0",
    "webpack-blocks": "1.0.0-rc.2",
    "webpack-bundle-analyzer": "^2.13.1",
    "webpack-dev-server": "^2.11.2",
    "webpack-manifest-plugin": "^1.3.2"
  },
  "optionalDependencies": {
    "ttab": "^0.6.1"
  },
  "jest": {
    "collectCoverageFrom": [
      "src/**/*.js",
      "!src/**/index.js",
      "!node_modules/**"
    ],
    "testMatch": [
      "**/?(*\\.)(test)\\.js?(x)"
    ],
    "setupFiles": [
      "./config/jest/shim.js",
      "./config/jest/setup.js"
    ],
    "snapshotSerializers": [
      "enzyme-to-json/serializer"
    ],
    "moduleDirectories": [
      "node_modules",
      "src"
    ],
    "moduleNameMapper": {
      "\\.css$": "identity-obj-proxy"
    },
    "transform": {
      "^.+\\.js$": "babel-jest"
    },
    "transformIgnorePatterns": [
      "node_modules/(?!@mde-ui|@trackking).+\\.js$"
    ]
  }
}

位于项目根目录的build.gradle文件如下:

import de.foobar.xliff.Translator
import foobar.Refdata

import java.time.LocalDateTime
import java.time.temporal.ChronoUnit
import java.util.function.UnaryOperator

import static java.time.temporal.ChronoUnit.*

apply plugin: 'java'
apply plugin: 'application'
apply plugin: 'com.github.johnrengelman.shadow'
apply plugin: 'findbugs'
apply plugin: 'pmd'
apply plugin: 'com.github.ben-manes.versions'
apply plugin: 'de.undercouch.download'
apply plugin: 'idea'

sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8

applicationDefaultJvmArgs = ["-XX:+UnlockCommercialFeatures", "-XX:+FlightRecorder"]
mainClassName = "foobar.app.Server"

def resourcesDir = sourceSets.main.output.resourcesDir
def xliffDir = file("$resourcesDir/xliff")
def libDir = file("$rootProject.projectDir/lib")
def soyToolsDir = file("$libDir/soyTools")

tasks.withType(FindBugs) {
    reports {
        xml.enabled = false
        html.enabled = true
    }
}

findbugs {
    toolVersion = '3.0.1'
    excludeFilter = file("$rootProject.projectDir/config/findbugs-exclude.xml")
    sourceSets = [sourceSets.main]
    showProgress = true
}

pmd {
    toolVersion = '6.2.0'
    ruleSets = []
    ruleSetFiles = files("$rootProject.projectDir/config/pmd.xml")
}

tasks.withType(JavaCompile) {
    options.compilerArgs << '-Xlint:unchecked'
    options.encoding = 'UTF-8'
}

ext {
    gradle_version = '4.9'
    tomcat_version = '8.0.51'
    slf4j_version = '1.7.25'
    log4j2_version = '2.11.0'
    metrics_version = '3.2.6'
    guice_version = '4.2.0'
    rewrite_version = '2.0.12.Final'
    soytemplates_version = '2017-08-08'
    guava_version = '24.1.1-jre'
    optimizely_version = '1.8.0'

    build_time = LocalDateTime.now().truncatedTo(MINUTES).toString()
    generatedSrcDir = "src/main/webapp/generated"
}

repositories {
    flatDir {
        dirs 'lib'
    }
    maven { url '...' }
    mavenLocal()
}

buildscript {

    repositories {
        maven { url '...' }
        mavenLocal()
    }

    dependencies {
        classpath 'args4j:args4j:2.33'
        classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.1'
        classpath 'com.github.ben-manes:gradle-versions-plugin:0.15.0'
        classpath 'de.undercouch:gradle-download-task:3.2.0'
    }
}

configurations.all {
    resolutionStrategy.eachDependency {
        if (it.requested.name == 'commons-logging') {
            it.useTarget 'org.slf4j:jcl-over-slf4j:1.7.16'
        }
    }
}

dependencies {
    compile group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4j2_version
    compile group: 'org.apache.logging.log4j', name: 'log4j-slf4j-impl', version: log4j2_version
    compile group: 'org.slf4j', name: 'jul-to-slf4j', version: slf4j_version
    compile group: 'com.google.template', name: 'soy', version: soytemplates_version
    compile group: 'com.google.code.gson', name: 'gson', version: '2.8.3'
    compile group: 'com.google.inject', name: 'guice', version: guice_version
    compile group: 'com.google.inject.extensions', name: 'guice-multibindings', version: guice_version
    compile group: 'com.google.inject.extensions', name: 'guice-assistedinject', version: guice_version
    compile group: 'com.google.guava', name: 'guava', version: guava_version
    compile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-core', version: tomcat_version
    compile group: 'org.apache.tomcat.embed', name: 'tomcat-embed-logging-juli', version: tomcat_version
    compile group: 'io.dropwizard.metrics', name: 'metrics-core', version: metrics_version
    compile group: 'io.dropwizard.metrics', name: 'metrics-servlets', version: metrics_version
    compile group: 'io.dropwizard.metrics', name: 'metrics-graphite', version: metrics_version
    compile group: 'org.ocpsoft.rewrite', name: 'rewrite-servlet', version: rewrite_version
    compile group: 'org.ocpsoft.rewrite', name: 'rewrite-config-proxy', version: rewrite_version
    compile group: 'com.google.template', name: 'soy', version: soytemplates_version
    compile group: 'com.optimizely.ab', name: 'core-api', version: optimizely_version
    compile group: 'com.optimizely.ab', name: 'core-httpclient-impl', version: optimizely_version

    compile name: 'j2v8_linux_x86_64-master-4.9.0-SNAPSHOT'
    compile name: 'j2v8_macos_x86_64-master-4.9.0-SNAPSHOT'

    compile group: 'com.spencerwi', name: 'Either.java', version: '1.1.0'
    compile group: 'org.apache.commons', name: 'commons-pool2', version: '2.4.2'

    compile 'org.apache.httpcomponents:httpcore:4.4.9'
    compile 'org.apache.httpcomponents:httpclient:4.5.5'

    compile 'com.squareup.retrofit:retrofit:1.8.2'
    compile 'io.reactivex:rxjava:1.3.8'
    compile 'io.reactivex:rxjava-string:1.1.1'

    runtime group: 'com.lmax', name: 'disruptor', version: '3.3.6'

    testCompile group: 'org.hamcrest', name: 'hamcrest-all', version: '1.3'
    testCompile group: 'junit', name: 'junit', version: '4.12'
    testCompile group: 'org.easytesting', name: 'fest-assert', version: '1.4'
    testCompile group: 'org.mockito', name: 'mockito-core', version: '1.10.19'
}

shadowJar {
    baseName = rootProject.name
    classifier = ''
    exclude 'META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat'
}

// build custom Soy functions to compile to Soy JavaScript, to run again if new functions are added
task soyFunctions(dependsOn: classes, type: Jar) {
    baseName = "soyFunctions"
    from sourceSets.main.output
    include "foobar/soy/Functions*"

    destinationDir = libDir
}

task soyExtractMsgs(type: Exec) {
    def relativeSoyFilesPrefix = "src/main/resources/soy"
    def absoluteSoyFilesPrefix = file(relativeSoyFilesPrefix).path + "/"

    List<String> inputs = new FileNameFinder()
            .getFileNames(relativeSoyFilesPrefix, "**/*.soy")

    // absolute paths to relative (to absoluteSoyFilesPrefix) paths
    inputs.replaceAll(new UnaryOperator<String>() {
        @Override
        String apply(String s) {
            return s.replace(absoluteSoyFilesPrefix, "")
        }
    })

    def soyArgs = ([
            "--outputFile", "$xliffDir/messages.xlf".toString(),
            "--sourceLocaleString", "de",
            "--inputPrefix", absoluteSoyFilesPrefix
    ] << inputs).flatten()

    def commandArgs = soyArgs.join(" ")

    commandLine "sh", "-c", "java -jar $soyToolsDir/SoyMsgExtractor.jar ${commandArgs}"

    // leave empty -> https://discuss.gradle.org/t/why-does-a-type-exec-task-require-that-one-not-use/6871/4
    doLast {
    }
}

task soyTranslate {
    doLast {
        def trArgs = [
                "--src", "$xliffDir/messages.xlf".toString(),
                "--mappingSrc", "config/soy/passolo-mapping.json",
                "--targetDir", "$xliffDir".toString()
        ]

        Translator.main(trArgs.toArray(new String[trArgs.size()]))
    }
}

task soyTranslateAll {
    mkdir(xliffDir)

    dependsOn soyExtractMsgs
    dependsOn soyTranslate

    // leave empty -> https://discuss.gradle.org/t/why-does-a-type-exec-task-require-that-one-not-use/6871/4
    doLast {
    }
}

// manual task to download latest soy tools
task soyDownloadTools {
    def soyMavenUrl = "http://repo1.maven.org/maven2/com/google/template/soy/$soytemplates_version"

    def soyMsgExtractorName = 'SoyMsgExtractor.jar'
    def soyToJsSrcCompilerName = 'SoyToJsSrcCompiler.jar'

    def soyFilePrefix = "soy-$soytemplates_version"
    def soyMsgExtractorFileName = "$soyFilePrefix-$soyMsgExtractorName"
    def soyJsSrcCompilerFileName = "$soyFilePrefix-$soyToJsSrcCompilerName"

    def soyJarFiles = [
        soyMsgExtractorName:"$soyMavenUrl/$soyMsgExtractorFileName",
        soyToJsSrcCompilerName:"$soyMavenUrl/$soyJsSrcCompilerFileName"
    ]

    doLast {
        delete soyToolsDir

        for (soyJarFile in soyJarFiles) {
            download {
                def destinationFileName = soyJarFile.key
                def sourceFileName = soyJarFile.value

                src sourceFileName
                dest new File(soyToolsDir, destinationFileName)
                onlyIfNewer true
                acceptAnyCertificate true
            }
        }
    }
}

task soyUpgrade {
    dependsOn soyDownloadTools
    dependsOn soyFunctions
}

// deprecated, use soyTranslateAll instead, will be removed soon
task translate {
    dependsOn soyTranslateAll

    // leave empty -> https://discuss.gradle.org/t/why-does-a-type-exec-task-require-that-one-not-use/6871/4
    doLast {
    }
}

task generateReferenceData {
    doFirst {
        def outDir = new File("build/tmp/resources/main/refdata")
        def list = ['1',
                    '2']

        list.each { l ->
            Refdata.translateJson(new File("config/refdata/${l}.json"), outDir)
        }
    }
}

task copyAssets(type: Copy) {
    destinationDir = resourcesDir

    from('src/main/resources/soy') {
        into("soy")
    }
    from('node_modules/react-header/dist/') {
        include '*.html'
        into("header")
    }
    from('node_modules/mde-react-footer/dist/') {
        include '*.html'
        into("footer")
    }
    from('build/tmp/resources/main/static') {
        into("static")
    }
    from('build/tmp/resources/main/refdata') {
        into("refdata")
    }
    doLast {
        println "copied assets to ${destinationDir}"
    }
}

task copyTestAssets(type: Copy) {
    destinationDir = sourceSets.test.output.resourcesDir

    from(resourcesDir)
    from("${rootProject.projectDir}/src/test/resources")

    doLast {
        println "copied test assets to ${destinationDir}"
    }
}

task copyTestAssets2(type: Copy) {
    destinationDir = new File("${buildDir}/classes/java/test")

    from(resourcesDir)
    from("${rootProject.projectDir}/src/test/resources")

    doLast {
        println "copied test assets to ${destinationDir}"
    }
}

task showProjectProps {
    doLast {
        println(project.ext.properties)
    }
}

task showBuildscriptClassPath {
    doLast {
        buildscript.configurations.classpath.each { println it }
    }
}

task prod {
    dependsOn build
    dependsOn soyTranslateAll
    dependsOn shadowJar
}

defaultTasks 'shadowJar'

processResources {
    dependsOn generateReferenceData
}

processTestResources {
    dependsOn generateReferenceData
}

compileTestJava {
    dependsOn copyAssets
    dependsOn copyTestAssets
    dependsOn copyTestAssets2
}

shadowJar {
    dependsOn soyTranslateAll
    dependsOn copyAssets
}

构建项目时,我们只调用npm i && npm run prod:build即可触发几个构建步骤,并在最后基本执行./gradlew prod。最后一步还执行了测试。这些测试的前提条件是复制同样执行的资产,并且所有资产都在相应的目标目录中。尽管如此,在执行测试时,Gradle会抛出错误,指出找不到已生成且位于正确位置的参考数据。

./gradlew test --rerun-tasks失败后执行npm i && npm run prod:build时,将找到参考数据以及其他资源,并且测试成功执行。

0 个答案:

没有答案