diff --git a/docs/reference/feature-flags.md b/docs/reference/feature-flags.md index 33591d250f..8d84b44e55 100644 --- a/docs/reference/feature-flags.md +++ b/docs/reference/feature-flags.md @@ -23,6 +23,8 @@ Feature flags with the `nextflow.preview` prefix can cause pipelines run with ne : When `true`, enables the use of modules with binary scripts. See {ref}`module-binaries` for more information. `nextflow.enable.strict` +: :::{deprecated} 26.04.0 + ::: : When `true`, executes the pipeline in "strict" mode, which introduces the following rules: - When reading a params file, Nextflow will fail if a dynamic param value references an undefined variable diff --git a/docs/strict-syntax.md b/docs/strict-syntax.md index 284a523f8c..2ac9323ad8 100644 --- a/docs/strict-syntax.md +++ b/docs/strict-syntax.md @@ -12,6 +12,10 @@ If you are still using DSL1, see {ref}`dsl1-page` to learn how to migrate your N The strict syntax can be enabled in Nextflow by setting the environment variable `NXF_SYNTAX_PARSER=v2`. ::: +:::{versionchanged} 26.04.0 +The strict syntax is enabled by default. It can be disabled by setting the environment variable `NXF_SYNTAX_PARSER=v1`. +::: + ## Overview The strict syntax is a subset of DSL2. While DSL2 allows any Groovy syntax, the strict syntax allows only a subset of Groovy syntax for Nextflow scripts and config files. This new specification enables more specific error reporting, ensures more consistent code, and will allow the Nextflow language to evolve independently of Groovy. diff --git a/modules/nextflow/src/main/groovy/nextflow/NF.groovy b/modules/nextflow/src/main/groovy/nextflow/NF.groovy index 4681be037c..89f5eed25e 100644 --- a/modules/nextflow/src/main/groovy/nextflow/NF.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/NF.groovy @@ -33,7 +33,7 @@ class NF { } static String getSyntaxParserVersion() { - return SysEnv.get('NXF_SYNTAX_PARSER', 'v1') + return SysEnv.get('NXF_SYNTAX_PARSER', 'v2') } static boolean isSyntaxParserV2() { diff --git a/modules/nextflow/src/main/groovy/nextflow/config/parser/v2/ConfigDsl.groovy b/modules/nextflow/src/main/groovy/nextflow/config/parser/v2/ConfigDsl.groovy index aeed4ec418..f84cee6119 100644 --- a/modules/nextflow/src/main/groovy/nextflow/config/parser/v2/ConfigDsl.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/config/parser/v2/ConfigDsl.groovy @@ -42,6 +42,8 @@ class ConfigDsl extends Script { private boolean strict + private boolean stripSecrets + private Path configPath private Map paramOverrides @@ -66,6 +68,10 @@ class ConfigDsl extends Script { this.strict = value } + void setStripSecrets(boolean value) { + this.stripSecrets = value + } + void setConfigPath(Path path) { this.configPath = path } @@ -195,6 +201,7 @@ class ConfigDsl extends Script { .setIgnoreIncludes(ignoreIncludes) .setRenderClosureAsString(renderClosureAsString) .setStrict(strict) + .setStripSecrets(stripSecrets) .setBinding(binding.getVariables()) .setParams(target.params as Map) .setProfiles(profiles) diff --git a/modules/nextflow/src/main/groovy/nextflow/config/parser/v2/ConfigParserV2.groovy b/modules/nextflow/src/main/groovy/nextflow/config/parser/v2/ConfigParserV2.groovy index ac9b8983b5..92463ba62e 100644 --- a/modules/nextflow/src/main/groovy/nextflow/config/parser/v2/ConfigParserV2.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/config/parser/v2/ConfigParserV2.groovy @@ -79,7 +79,7 @@ class ConfigParserV2 implements ConfigParser { } @Override - ConfigParser setStripSecrets(boolean value) { + ConfigParserV2 setStripSecrets(boolean value) { this.stripSecrets = value return this } @@ -127,9 +127,11 @@ class ConfigParserV2 implements ConfigParser { if( path ) script.setConfigPath(path) script.setIgnoreIncludes(ignoreIncludes) + script.setRenderClosureAsString(renderClosureAsString) + script.setStrict(strict) + script.setStripSecrets(stripSecrets) script.setParams(paramOverrides) script.setProfiles(appliedProfiles) - script.setRenderClosureAsString(renderClosureAsString) script.run() final target = script.getTarget() diff --git a/modules/nextflow/src/main/groovy/nextflow/script/BaseScript.groovy b/modules/nextflow/src/main/groovy/nextflow/script/BaseScript.groovy index 376baa9253..b3cab745a8 100644 --- a/modules/nextflow/src/main/groovy/nextflow/script/BaseScript.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/script/BaseScript.groovy @@ -40,16 +40,12 @@ abstract class BaseScript extends Script implements ExecutionContext { private Session session - private ProcessFactory processFactory - private ScriptMeta meta private WorkflowDef entryFlow private OutputDef publisher - @Lazy InputStream stdin = { System.in }() - BaseScript() { meta = ScriptMeta.register(this) } @@ -90,7 +86,6 @@ abstract class BaseScript extends Script implements ExecutionContext { private void setup() { binding.owner = this session = binding.getSession() - processFactory = session.newProcessFactory(this) binding.setVariable( 'baseDir', session.baseDir ) binding.setVariable( 'projectDir', session.baseDir ) diff --git a/modules/nextflow/src/main/groovy/nextflow/script/BaseScriptConsts.groovy b/modules/nextflow/src/main/groovy/nextflow/script/BaseScriptConsts.groovy index 6e69964e9f..5ef7ee7a00 100644 --- a/modules/nextflow/src/main/groovy/nextflow/script/BaseScriptConsts.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/script/BaseScriptConsts.groovy @@ -25,5 +25,5 @@ class BaseScriptConsts { public static Object[] EMPTY_ARGS = [] as Object[] - public static List PRIVATE_NAMES = ['session','processFactory','taskProcessor','meta','entryFlow', 'publisher'] + public static List PRIVATE_NAMES = ['session','meta','entryFlow', 'publisher'] } diff --git a/modules/nextflow/src/main/groovy/nextflow/script/WorkflowDef.groovy b/modules/nextflow/src/main/groovy/nextflow/script/WorkflowDef.groovy index 3aec93a45b..712c040ddd 100644 --- a/modules/nextflow/src/main/groovy/nextflow/script/WorkflowDef.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/script/WorkflowDef.groovy @@ -198,14 +198,17 @@ class WorkflowDef extends BindableDef implements ChainableDef, IterableDef, Exec private Object run0(Object[] args) { collectInputs(binding, args) - // invoke the workflow execution final closure = body.closure closure.setDelegate(binding) closure.setResolveStrategy(Closure.DELEGATE_FIRST) - closure.call() - // collect the workflow outputs - output = collectOutputs(declaredOutputs) - return output + final result = closure.call() + if( name == null ) { + return result + } + else { + output = collectOutputs(declaredOutputs) + return output + } } } diff --git a/modules/nextflow/src/main/groovy/nextflow/script/parser/v2/ScriptLoaderV2.groovy b/modules/nextflow/src/main/groovy/nextflow/script/parser/v2/ScriptLoaderV2.groovy index ed64615074..e4ef2db191 100644 --- a/modules/nextflow/src/main/groovy/nextflow/script/parser/v2/ScriptLoaderV2.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/script/parser/v2/ScriptLoaderV2.groovy @@ -42,6 +42,8 @@ class ScriptLoaderV2 implements ScriptLoader { private boolean skipEntryFlow + private Object result + ScriptLoaderV2(Session session) { this.session = session } @@ -69,7 +71,9 @@ class ScriptLoaderV2 implements ScriptLoader { } @Override - Object getResult() { null } + Object getResult() { + return result + } @Override ScriptLoaderV2 parse(Path scriptPath) { @@ -83,14 +87,15 @@ class ScriptLoaderV2 implements ScriptLoader { } ScriptLoaderV2 parse(String scriptText) { - return parse0(scriptText, null) + parse0(scriptText, null) + return this } @Override ScriptLoaderV2 runScript() { assert session assert mainScript - mainScript.run() + this.result = mainScript.run() return this } @@ -104,17 +109,17 @@ class ScriptLoaderV2 implements ScriptLoader { private void parse0(String scriptText, Path scriptPath) { final compiler = getCompiler() try { - final result = scriptPath + final compileResult = scriptPath ? compiler.compile(scriptPath.toFile()) : compiler.compile(scriptText) - mainScript = createScript(result.main(), session.binding, scriptPath, skipEntryFlow) + this.mainScript = createScript(compileResult.main(), session.binding, scriptPath, skipEntryFlow) - result.modules().forEach((path, clazz) -> { + compileResult.modules().forEach((path, clazz) -> { createScript(clazz, new ScriptBinding(), path, true) }) - for( final name : result.processNames() ) + for( final name : compileResult.processNames() ) ScriptMeta.addResolvedName(name) } catch( CompilationFailedException e ) { diff --git a/modules/nextflow/src/test/groovy/FunctionalTests.groovy b/modules/nextflow/src/test/groovy/FunctionalTests.groovy index 14318032cd..32e5a1d8a4 100644 --- a/modules/nextflow/src/test/groovy/FunctionalTests.groovy +++ b/modules/nextflow/src/test/groovy/FunctionalTests.groovy @@ -14,13 +14,13 @@ * limitations under the License. */ -import nextflow.config.ConfigParserFactory import nextflow.exception.AbortRunException import nextflow.processor.TaskProcessor import nextflow.util.MemoryUnit import spock.lang.Timeout import test.Dsl2Spec -import test.MockScriptRunner + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso @@ -28,35 +28,10 @@ import test.MockScriptRunner @Timeout(10) class FunctionalTests extends Dsl2Spec { - /* - * test passing values through command line argument (unnamed parameters) - */ - def 'test args'() { - - given: - def script = """ - def len = args.size() - def x = args[0] - def y = args[1] - - return [ x, y, len ] - """ - - when: - def result = new MockScriptRunner().setScript(script).execute(['hello', 'hola']) - - then: - result[0] == 'hello' - result[1] == 'hola' - result[2] == 2 - - } - - def 'test configure processor'() { given: - def config = ''' + def config = loadConfig(''' process { dummyField = 99 executor = 'nope' @@ -65,7 +40,7 @@ class FunctionalTests extends Dsl2Spec { maxForks = 10 environment = [a:1, b:2,c:3] } - ''' + ''') and: def script = ''' @@ -73,6 +48,8 @@ class FunctionalTests extends Dsl2Spec { process taskHello { debug true maxForks 11 + + script: 'echo hello' } @@ -80,9 +57,7 @@ class FunctionalTests extends Dsl2Spec { ''' when: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) def processor = TaskProcessor.currentProcessor() then: @@ -99,9 +74,9 @@ class FunctionalTests extends Dsl2Spec { def 'should define default ext property' () { given: - def config = ''' + def config = loadConfig(''' process.ext.foo = 'hello' - ''' + ''') and: def script = ''' @@ -116,9 +91,7 @@ class FunctionalTests extends Dsl2Spec { ''' when: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) def processor = TaskProcessor.currentProcessor() then: processor instanceof TaskProcessor @@ -129,7 +102,7 @@ class FunctionalTests extends Dsl2Spec { def 'test merge ext properties' () { given: - def config = ''' + def config = loadConfig(''' process { ext { alpha = "aaa" @@ -142,7 +115,7 @@ class FunctionalTests extends Dsl2Spec { } } } - ''' + ''') and: def script = ''' @@ -155,9 +128,7 @@ class FunctionalTests extends Dsl2Spec { ''' when: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) def processor = TaskProcessor.currentProcessor() then: processor instanceof TaskProcessor @@ -170,14 +141,14 @@ class FunctionalTests extends Dsl2Spec { def 'test configure processor with dynamic resources'() { setup: - def config = ''' + def config = loadConfig(''' process { cpus = { 2 * task.attempt } memory = { 1.GB * task.attempt } time = { 1.h * task.attempt } withName: taskHello{ errorStrategy = 'finish' } } - ''' + ''') and: def script = ''' @@ -196,9 +167,7 @@ class FunctionalTests extends Dsl2Spec { ''' when: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) def processor = TaskProcessor.currentProcessor() then: @@ -217,47 +186,45 @@ class FunctionalTests extends Dsl2Spec { * A config with a `memory` definition for all process * and two labels `small` and `big` */ - String config = ''' + def config = loadConfig(''' process { executor = 'nope' memory = 2.GB - + withLabel: small { - cpus = 2 + cpus = 2 queue = 'the-small-one' } - + withLabel: big { - cpus = 8 + cpus = 8 memory = 4.GB - queue = 'big-partition' + queue = 'big-partition' } - + withName: legacy { - cpus = 3 + cpus = 3 queue = 'legacy-queue' } } - ''' + ''') and: /* * no label is specified it should only use default directives */ - String script = ''' + def script = ''' process foo { script: 'echo hello' } - + workflow { foo() } ''' when: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) def processor = TaskProcessor.currentProcessor() then: @@ -270,21 +237,19 @@ class FunctionalTests extends Dsl2Spec { /* * the `small` label is applied */ - script = ''' + script = ''' process foo { label 'small' script: 'echo hello' } - + workflow { foo() } ''' and: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) processor = TaskProcessor.currentProcessor() then: @@ -304,13 +269,11 @@ class FunctionalTests extends Dsl2Spec { script: 'echo hello' } - + workflow { foo() } ''' and: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) processor = TaskProcessor.currentProcessor() then: @@ -327,19 +290,17 @@ class FunctionalTests extends Dsl2Spec { */ script = ''' process legacy { - cpus 1 + cpus 1 queue 'one' script: 'echo hello' } - + workflow { legacy() } ''' and: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) processor = TaskProcessor.currentProcessor() then: @@ -356,42 +317,40 @@ class FunctionalTests extends Dsl2Spec { * A config with a `memory` definition for all process * and two labels `small` and `big` */ - String config = ''' + def config = loadConfig(''' process { - executor = 'nope' - + executor = 'nope' + withLabel: small { - cpus = 2 + cpus = 2 queue = 'the-small-one' } - + withName: bar { - cpus = 8 + cpus = 8 memory = 4.GB - queue = 'big-partition' + queue = 'big-partition' } } - ''' + ''') and: /* * no label is specified it should only use default directives */ - String script = ''' + def script = ''' process foo { label 'small' script: 'echo hello' } - + workflow { foo() } ''' when: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) def processor = TaskProcessor.currentProcessor() then: @@ -404,20 +363,18 @@ class FunctionalTests extends Dsl2Spec { /* * no label is specified it should only use default directives */ - script = ''' + script = ''' process bar { label 'small' script: 'echo hello' } - + workflow { bar() } ''' and: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) processor = TaskProcessor.currentProcessor() then: @@ -428,29 +385,27 @@ class FunctionalTests extends Dsl2Spec { def 'should set module directive' () { given: - String config = ''' + def config = loadConfig(''' process { executor = 'nope' withLabel: 'my_env' { module = 'ncbi-blast/2.2.27:t_coffee/10.0:clustalw/2.1' } } - ''' + ''') and: - String script = ''' + def script = ''' process foo { module 'mod-a/1.1:mod-b/2.2' script: 'echo hello' } - + workflow { foo() } ''' when: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) def processor = TaskProcessor.currentProcessor() then: processor instanceof TaskProcessor @@ -459,29 +414,27 @@ class FunctionalTests extends Dsl2Spec { when: - config = ''' + config = loadConfig(''' process { executor = 'nope' withLabel: 'my_env' { module = 'ncbi-blast/2.2.27:t_coffee/10.0:clustalw/2.1' } } - ''' + ''') and: - script = ''' + script = ''' process foo { label 'my_env' script: 'echo hello' } - + workflow { foo() } ''' and: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) processor = TaskProcessor.currentProcessor() then: @@ -493,25 +446,23 @@ class FunctionalTests extends Dsl2Spec { def 'should set publishDir directive' () { given: - String config = ''' + def config = loadConfig(''' process { executor = 'nope' publishDir = '/some/dir' } - ''' - String script = ''' + ''') + def script = ''' process foo { script: 'echo hello' } - + workflow { foo() } ''' when: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) def processor = TaskProcessor.currentProcessor() then: processor instanceof TaskProcessor @@ -519,25 +470,23 @@ class FunctionalTests extends Dsl2Spec { when: - config = ''' + config = loadConfig(''' process { executor = 'nope' publishDir = [ '/some/dir', [path:'/other/dir', mode: 'copy'] ] } - ''' + ''') and: - script = ''' + script = ''' process foo { script: 'echo hello' } - + workflow { foo() } ''' and: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) processor = TaskProcessor.currentProcessor() then: processor instanceof TaskProcessor @@ -546,27 +495,25 @@ class FunctionalTests extends Dsl2Spec { when: - config = ''' + config = loadConfig(''' process { executor = 'nope' } - ''' + ''') and: - script = ''' + script = ''' process foo { publishDir '/data1' publishDir '/data2', mode: 'symlink' script: 'echo hello' } - + workflow { foo() } ''' and: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) processor = TaskProcessor.currentProcessor() then: processor instanceof TaskProcessor @@ -575,28 +522,26 @@ class FunctionalTests extends Dsl2Spec { when: - config = ''' + config = loadConfig(''' process { executor = 'nope' publishDir = '/dir/cfg' } - ''' + ''') and: - script = ''' + script = ''' process foo { publishDir '/dir/alpha' - publishDir '/dir/bravo' + publishDir '/dir/bravo' script: 'echo hello' } - + workflow { foo() } ''' and: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) processor = TaskProcessor.currentProcessor() then: processor instanceof TaskProcessor @@ -606,29 +551,27 @@ class FunctionalTests extends Dsl2Spec { when: - config = ''' + config = loadConfig(''' process { executor = 'nope' publishDir = '/dir/cfg' withName: foo { publishDir = '/dir/omega' } } - ''' + ''') and: - script = ''' + script = ''' process foo { publishDir '/dir/alpha' - publishDir '/dir/bravo' + publishDir '/dir/bravo' script: 'echo hello' } - + workflow { foo() } ''' and: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) processor = TaskProcessor.currentProcessor() then: processor instanceof TaskProcessor @@ -640,25 +583,23 @@ class FunctionalTests extends Dsl2Spec { def 'should set directive label' () { given: - def config = ''' + def config = loadConfig(''' process { executor = 'nope' label = 'alpha' } - ''' - def script = ''' + ''') + def script = ''' process foo { script: 'echo hello' } - + workflow { foo() } ''' when: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) def processor = TaskProcessor.currentProcessor() then: processor instanceof TaskProcessor @@ -666,28 +607,26 @@ class FunctionalTests extends Dsl2Spec { when: - config = ''' + config = loadConfig(''' process { executor = 'nope' label = 'alpha' } - ''' + ''') and: - script = ''' + script = ''' process foo { label 'bravo' - label 'gamma' + label 'gamma' script: 'echo hello' } - + workflow { foo() } ''' and: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) processor = TaskProcessor.currentProcessor() then: processor instanceof TaskProcessor @@ -698,27 +637,25 @@ class FunctionalTests extends Dsl2Spec { def 'should create process with repeater'() { given: - def config = ''' + def config = loadConfig(''' process { executor = 'nope' } - ''' + ''') and: - def script = ''' + def script = ''' process foo { input: each x script: 'echo hello' } - + workflow { foo([1,2,3]) } ''' when: - new MockScriptRunner(ConfigParserFactory.create().parse(config)) - .setScript(script) - .execute() + loadScript(script, config: config) then: noExceptionThrown() } @@ -727,15 +664,15 @@ class FunctionalTests extends Dsl2Spec { given: def script = '''/*1*/ -/*2*/ def thisMethodExpectsOnlyOneString(String a){ +/*2*/ def thisMethodExpectsOnlyOneString(String a) { /*3*/ a /*4*/ } -/*5*/ +/*5*/ /*6*/ process foo { /*7*/ input: /*8*/ each x /*9*/ script: -/*10*/ "${thisMethodExpectsOnlyOneString(1)}" +/*10*/ "${thisMethodExpectsOnlyOneString()}" /*11*/ } /*12*/ /*13*/ workflow { foo(1) } @@ -743,11 +680,11 @@ class FunctionalTests extends Dsl2Spec { when: def config = [process:[executor: 'nope']] - def runner = new MockScriptRunner(config) - runner.setScript(script).execute() + runScript(script, config) + def processor = TaskProcessor.currentProcessor() then: def abort = thrown(AbortRunException) and: - runner.session.fault.report ==~ /(?s).*-- Check script '(.*?)' at line: 10.*/ + processor.session.fault.report ==~ /(?s).*-- Check script '(.*?)' at line: 10.*/ } } diff --git a/modules/nextflow/src/test/groovy/nextflow/cli/CmdConfigTest.groovy b/modules/nextflow/src/test/groovy/nextflow/cli/CmdConfigTest.groovy index d6d4248afe..59fe67c52d 100644 --- a/modules/nextflow/src/test/groovy/nextflow/cli/CmdConfigTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/cli/CmdConfigTest.groovy @@ -119,7 +119,7 @@ class CmdConfigTest extends Specification { executor = 'slurm' queue = 'long' } - + docker { enabled = true } @@ -288,17 +288,17 @@ class CmdConfigTest extends Specification { author = 'me' mainScript = 'foo.nf' } - + process { queue = 'cn-el7' - cpus = 4 + cpus = 4 memory = 10.GB time = 5.h ext.other = { 10.GB * task.attempt } } ''' def buffer = new ByteArrayOutputStream() - // command definition + // command definition def cmd = new CmdConfig() cmd.launcher = new Launcher(options: new CliOptions(config: [CONFIG.toString()])) cmd.stdout = buffer @@ -306,14 +306,14 @@ class CmdConfigTest extends Specification { when: cmd.run() - + then: buffer.toString() == ''' manifest { author = 'me' mainScript = 'foo.nf' } - + process { queue = 'cn-el7' cpus = 4 @@ -372,61 +372,6 @@ class CmdConfigTest extends Specification { folder.deleteDir() } - def 'should handle variables' () { - given: - def folder = Files.createTempDirectory('test') - def CONFIG = folder.resolve('nextflow.config') - - CONFIG.text = ''' - X1 = 'SOMETHING' - X2 = [X1] - X3 = [p:111, q:'bbb'] - - params { - alpha = ["${X1}/a", "b", "c"] - delta = [ X2, 'z' ] - gamma = [p: "${X1}/a", q: X2, 'r-r': 'X1'] - omega = X3 - } - ''' - - def buffer = new ByteArrayOutputStream() - // command definition - def cmd = new CmdConfig() - cmd.launcher = new Launcher(options: new CliOptions(config: [CONFIG.toString()])) - cmd.stdout = buffer - cmd.args = [ '.' ] - - when: - cmd.run() - - then: - buffer.toString() == '''\ - X1 = 'SOMETHING' - X2 = ['SOMETHING'] - X3 = [p:111, q:'bbb'] - - params { - alpha = ['SOMETHING/a', 'b', 'c'] - delta = [['SOMETHING'], 'z'] - gamma = [p:'SOMETHING/a', q:['SOMETHING'], 'r-r':'X1'] - omega = [p:111, q:'bbb'] - } - '''.stripIndent() - - when: - def result = new ConfigSlurper().parse(buffer.toString()) - then: - result.X1 == 'SOMETHING' - result.X2 == ['SOMETHING'] - result.X3 == [p:111, q:'bbb'] - result.params.alpha == ['SOMETHING/a', 'b', 'c'] - result.params.delta == [['SOMETHING'], 'z'] - result.params.gamma == [p:'SOMETHING/a', q:['SOMETHING'], 'r-r': 'X1'] - result.params.omega == [p:111, q:'bbb'] - - } - @IgnoreIf({System.getenv('NXF_SMOKE')}) @Requires({System.getenv('NXF_GITHUB_ACCESS_TOKEN')}) def 'should resolve remote config' () { @@ -464,20 +409,18 @@ class CmdConfigTest extends Specification { params { foo = 'baz' } - + profiles { - test { - params { - foo = 'foo' - } - profiles { - debug { - cleanup = false - } - } - } - } - ''' + test { + params { + foo = 'foo' + } + } + debug { + cleanup = false + } + } + ''' def buffer = new ByteArrayOutputStream() // command definition @@ -512,7 +455,7 @@ class CmdConfigTest extends Specification { and: def CONFIG = folder.resolve('nextflow.config') CONFIG.text = ''' - process { + process { queue = secrets.MYSTERY } ''' @@ -549,11 +492,11 @@ class CmdConfigTest extends Specification { author = 'me' mainScript = 'foo.nf' } - + process { - cpus = 4 + cpus = 4 queue = 'cn-el7' - memory = { 10.GB } + memory = { 10.GB } ext.other = { 10.GB * task.attempt } } ''' @@ -600,11 +543,11 @@ class CmdConfigTest extends Specification { author = 'me' mainScript = 'foo.nf' } - + process { - cpus = 4 + cpus = 4 queue = 'cn-el7' - memory = { 10.GB } + memory = { 10.GB } ext.other = { 10.GB * task.attempt } } ''' diff --git a/modules/nextflow/src/test/groovy/nextflow/cli/CmdRunTest.groovy b/modules/nextflow/src/test/groovy/nextflow/cli/CmdRunTest.groovy index 333e20a9e6..0ab0729c1d 100644 --- a/modules/nextflow/src/test/groovy/nextflow/cli/CmdRunTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/cli/CmdRunTest.groovy @@ -35,10 +35,15 @@ class CmdRunTest extends Specification { @Unroll def 'should parse cmd param=#STR' () { + setup: + SysEnv.push(NXF_SYNTAX_PARSER: 'v1') expect: CmdRun.parseParamValue(STR) == EXPECTED + cleanup: + SysEnv.pop() + where: STR | EXPECTED null | null @@ -83,10 +88,10 @@ class CmdRunTest extends Specification { where: PARAMS | KEY | VALUE | EXPECTED - [:] | 'foo' | '1' | [foo: 1] - [foo: 1] | 'bar' | '2' | [foo: 1, bar: 2] + [:] | 'foo' | '1' | [foo: '1'] + [foo: 1] | 'bar' | '2' | [foo: 1, bar: '2'] [:] | 'x.y.z' | 'Hola' | [x: [y: [z: 'Hola']]] - [a: [p:1], x:3] | 'a.q' | '2' | [a: [p:1, q: 2], x:3] + [a: [p:1], x:3] | 'a.q' | '2' | [a: [p:1, q: '2'], x:3] [:] | /x\.y\.z/ | 'Hola' | ['x.y.z': 'Hola'] [:] | /x.y\.z/ | 'Hola' | ['x': ['y.z': 'Hola']] } @@ -98,7 +103,7 @@ class CmdRunTest extends Specification { CmdRun.addParam(params, 'alphaBeta', '1') CmdRun.addParam(params, 'alpha-beta', '10') then: - params['alphaBeta'] == 10 + params['alphaBeta'] == '10' !params.containsKey('alpha-beta') when: @@ -106,7 +111,7 @@ class CmdRunTest extends Specification { CmdRun.addParam(params, 'aaa-bbb-ccc', '1') CmdRun.addParam(params, 'aaaBbbCcc', '10') then: - params['aaaBbbCcc'] == 10 + params['aaaBbbCcc'] == '10' !params.containsKey('aaa-bbb-ccc') } diff --git a/modules/nextflow/src/test/groovy/nextflow/config/ConfigBuilderTest.groovy b/modules/nextflow/src/test/groovy/nextflow/config/ConfigBuilderTest.groovy index 0220ea7e2c..feeaab739f 100644 --- a/modules/nextflow/src/test/groovy/nextflow/config/ConfigBuilderTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/config/ConfigBuilderTest.groovy @@ -88,19 +88,38 @@ class ConfigBuilderTest extends Specification { setup: def builder = [:] as ConfigBuilder - def env = [HOME:'/home/my', PATH:'/local/bin', 'dot.key.name':'any text'] + and: + SysEnv.push(HOME:'/home/my', PATH:'/local/bin', 'dot.key.name':'any text') def text1 = ''' - task { field1 = 1; field2 = 'hola'; } - env { alpha = 'a1'; beta = 'b1'; HOME="$HOME:/some/path"; } + task { + field1 = 1 + field2 = 'hola' + } + + env { + alpha = 'a1' + beta = 'b1' + HOME = "${env('HOME')}:/some/path" + } ''' def text2 = ''' - task { field2 = 'Hello' } - env { beta = 'b2'; delta = 'd2'; HOME="$HOME:/local/path"; XXX="$PATH:/xxx"; YYY = "$XXX:/yyy"; WWW = "${WWW?:''}:value" } + task { + field2 = 'Hello' + } + + env { + beta = 'b2' + delta = 'd2' + HOME = "${env('HOME')}:/local/path" + XXX = "${env('PATH')}:/xxx" + WWW = "${env('WWW') ?: ''}:value" + } ''' when: + def env = SysEnv.get() def config1 = builder.buildConfig0(env, [text1]) def config2 = builder.buildConfig0(env, [text1, text2]) @@ -122,27 +141,41 @@ class ConfigBuilderTest extends Specification { config2.env.HOME == '/home/my:/local/path' config2.env.XXX == '/local/bin:/xxx' config2.env.PATH == '/local/bin' - config2.env.YYY == '/local/bin:/xxx:/yyy' config2.env.ZZZ == '99' config2.env.WWW == ':value' + cleanup: + SysEnv.pop() } def 'build config object 3' () { setup: def builder = [:] as ConfigBuilder - def env = [HOME:'/home/my', PATH:'/local/bin', 'dot.key.name':'any text'] + and: + SysEnv.push(HOME:'/home/my', PATH:'/local/bin', 'dot.key.name':'any text') def text1 = ''' - task { field1 = 1; field2 = 'hola'; } - env { alpha = 'a1'; beta = 'b1'; HOME="$HOME:/some/path"; } - params { demo = 1 } + task { + field1 = 1 + field2 = 'hola' + } + + env { + alpha = 'a1' + beta = 'b1' + HOME = "${env('HOME')}:/some/path" + } + + params { + demo = 1 + } params.test = 2 ''' when: + def env = SysEnv.get() def config1 = builder.buildConfig0(env, [text1]) then: @@ -155,6 +188,8 @@ class ConfigBuilderTest extends Specification { config1.params.test == 2 config1.params.demo == 1 + cleanup: + SysEnv.pop() } def 'build config object 4' () { @@ -169,7 +204,6 @@ class ConfigBuilderTest extends Specification { q = "$baseDir/2" x = "$projectDir/3" y = "$launchDir/4" - z = "$outputDir/5" } ''' @@ -180,7 +214,6 @@ class ConfigBuilderTest extends Specification { cfg.params.q == '/base/path/2' cfg.params.x == '/base/path/3' cfg.params.y == "${Path.of('.').toRealPath()}/4" - cfg.params.z == "${Path.of('results').complete()}/5" } @@ -287,7 +320,7 @@ class ConfigBuilderTest extends Specification { def config = configWithParams(configMain.toPath(), [params: [one: '1', two: 'dos', three: 'tres']]) then: - config.params.one == 1 + config.params.one == '1' config.params.two == 'dos' config.params.three == 'tres' config.process.name == 'alpha' @@ -1696,87 +1729,6 @@ class ConfigBuilderTest extends Specification { } - def 'should warn about missing attribute' () { - - given: - def file = Files.createTempFile('test','config') - file.deleteOnExit() - file.text = - ''' - params.foo = HOME - ''' - - - when: - SysEnv.push(HOME: '/home/user') - def opt = new CliOptions(config: [file.toFile().canonicalPath] ) - def cfg = new ConfigBuilder().setOptions(opt).build() - SysEnv.pop() - then: - cfg.params.foo == '/home/user' - - when: - file.text = - ''' - params.foo = bar - ''' - opt = new CliOptions(config: [file.toFile().canonicalPath] ) - new ConfigBuilder().setOptions(opt).build() - then: - def e = thrown(ConfigParseException) - e.message == "Unknown config attribute `bar` -- check config file: ${file.toRealPath()}".toString() - - } - - def 'should render missing variables' () { - given: - def file = Files.createTempFile('test',null) - - file.text = - ''' - foo = 'xyz' - bar = "$SCRATCH/singularity_images_nextflow" - ''' - - when: - def opt = new CliOptions(config: [file.toFile().canonicalPath] ) - def builder = new ConfigBuilder() - .setOptions(opt) - .showMissingVariables(true) - def cfg = builder.buildConfigObject() - def str = ConfigHelper.toCanonicalString(cfg) - then: - str == '''\ - foo = 'xyz' - bar = '$SCRATCH/singularity_images_nextflow' - '''.stripIndent() - - and: - builder.warnings[0].startsWith('Unknown config attribute `SCRATCH`') - cleanup: - file?.delete() - } - - def 'should report fully qualified missing attribute' () { - - given: - def file = Files.createTempFile('test','config') - file.deleteOnExit() - - when: - file.text = - ''' - params.x = foo.bar - ''' - def opt = new CliOptions(config: [file.toFile().canonicalPath] ) - new ConfigBuilder().setOptions(opt).build() - then: - def e = thrown(ConfigParseException) - e.message == "Unknown config attribute `foo.bar` -- check config file: ${file.toRealPath()}".toString() - - } - - def 'should collect config files' () { given: @@ -2470,9 +2422,7 @@ class ConfigBuilderTest extends Specification { test { includeConfig 'test.conf' } } - if (params.load_config) { - includeConfig 'process.conf' - } + includeConfig (params.load_config ? 'process.conf' : '/dev/null') ''' test.text = ''' params { diff --git a/modules/nextflow/src/test/groovy/nextflow/extension/BranchOpTest.groovy b/modules/nextflow/src/test/groovy/nextflow/extension/BranchOpTest.groovy index 7425677d96..301fcf8121 100644 --- a/modules/nextflow/src/test/groovy/nextflow/extension/BranchOpTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/extension/BranchOpTest.groovy @@ -24,6 +24,8 @@ import nextflow.Channel import nextflow.exception.ScriptCompilationException import test.Dsl2Spec import test.OutputCapture + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso @@ -36,9 +38,8 @@ class BranchOpTest extends Dsl2Spec { def 'should branch input values' () { when: - def result = dsl_eval(''' - Channel - .from(0,1,2) + def result = runScript(''' + channel.of(0,1,2) .branch { foo: it <1 bar: it == 1 @@ -60,9 +61,8 @@ class BranchOpTest extends Dsl2Spec { def 'should branch and capture default' () { when: - def result = dsl_eval(''' - Channel - .from(10,20,30) + def result = runScript(''' + channel.of(10,20,30) .branch { foo: it <=10 bar: true @@ -82,9 +82,8 @@ class BranchOpTest extends Dsl2Spec { def 'should branch and return empty channel' () { when: - def result = dsl_eval(''' - Channel - .from(1,2,3) + def result = runScript(''' + channel.of(1,2,3) .branch { foo: it <=10 bar: true @@ -105,15 +104,14 @@ class BranchOpTest extends Dsl2Spec { def 'should branch and set' () { when: - dsl_eval(''' - Channel - .from(1,2,3,40,50) + runScript(''' + channel.of(1,2,3,40,50) .branch { small: it < 10 large: it > 10 } .set { result } - + result.small.view { "small:$it" } result.large.view { "large:$it" } ''') @@ -129,12 +127,11 @@ class BranchOpTest extends Dsl2Spec { def 'should branch and pipe set' () { when: - dsl_eval(''' - Channel - .from(1,2,3,40,50) \ + runScript(''' + channel.of(1,2,3,40,50) \ | branch { small: it < 10; large: it > 10 } \ | set { result } - + result.small.view { "small:$it" } result.large.view { "large:$it" } ''') @@ -152,9 +149,8 @@ class BranchOpTest extends Dsl2Spec { def 'should branch and return custom values' () { when: - def result = dsl_eval(''' - Channel - .from(0,1,2) + def result = runScript(''' + channel.of(0,1,2) .branch { foo: it <1 bar: it == 1; return 10 @@ -176,12 +172,11 @@ class BranchOpTest extends Dsl2Spec { def 'should handle complex nested return statement' () { when: - def result = dsl_eval(''' - Channel - .from(-1,0,1) + def result = runScript(''' + channel.of(-1,0,1) .branch { foo: true - if( it == 0 ) { return 'zero' } + if( it == 0 ) { return 'zero' } else if( it<0 ) return 'less than zero' else { return 'great than zero' } } @@ -193,15 +188,14 @@ class BranchOpTest extends Dsl2Spec { result.val == Channel.STOP } - @Ignore // this is not supported and require explicit use of `return` + @Ignore // this is not supported and require explicit use of `return` def 'should handle complex expression statement' () { when: - def result = dsl_eval(''' - Channel - .from(-1,0,1) + def result = runScript(''' + channel.of(-1,0,1) .branch { foo: true - if( it == 0 ) { 'zero' } + if( it == 0 ) { 'zero' } else if( it<0 ) 'less than zero' else { 'great than zero' } } @@ -218,13 +212,12 @@ class BranchOpTest extends Dsl2Spec { def 'should branch and return last expression' () { when: - def result = dsl_eval(''' - Channel - .from(0,1,2) + def result = runScript(''' + channel.of(0,1,2) .branch { foo: it <1 bar: it == 1; it * 2 + it - baz: it >1; it * 2 + it + baz: it >1; it * 2 + it } ''') then: @@ -243,10 +236,9 @@ class BranchOpTest extends Dsl2Spec { def 'should branch on pair argument' () { when: - def result = dsl_eval(''' - Channel - .from(['a', 1], ['b', 2]) - .branch { key, value -> + def result = runScript(''' + channel.of(['a', 1], ['b', 2]) + .branch { key, value -> foo: key=='a'; return value bar: true } @@ -263,86 +255,71 @@ class BranchOpTest extends Dsl2Spec { def 'should pass criteria as argument' () { when: - dsl_eval(''' - criteria = branchCriteria { + runScript(''' + criteria = branchCriteria { foo: it<5 bar: it>=5 } - bra1 = Channel.of(1,2,3).branch(criteria) - bra2 = Channel.of(6,7,8).branch(criteria) - + bra1 = channel.of(1,2,3).branch(criteria) + bra2 = channel.of(6,7,8).branch(criteria) + bra1.foo.view { "foo:$it" } bra2.bar.view { "bar:$it" } ''') - + def stdout = capture.toString() then: stdout.contains('foo:1') } - def 'should error since param is missing' () { - when: - dsl_eval(''' - Channel - .from(1) - .branch { -> - foo: false - bar: true - } - ''') - then: - def e = thrown(ScriptCompilationException) - e.message.contains 'Branch criteria should declare at least one parameter or use the implicit `it` parameter' - } - def 'should error due to dup label' () { when: - dsl_eval(''' - Channel.empty() .branch { foo: true; foo: true } + runScript(''' + channel.empty() .branch { foo: true; foo: true } ''') then: def e = thrown(ScriptCompilationException) - e.message.contains 'Branch label already declared: foo' + e.cause.message.contains 'Branch label already declared: foo' } def 'should error due to invalid bool expr' () { when: - dsl_eval(''' - Channel.empty() .branch { foo: if(it) {}; bar: true } + runScript(''' + channel.empty() .branch { foo: if(it) {}; bar: true } ''') then: def e = thrown(ScriptCompilationException) - e.message.contains 'Unexpected statement' + e.cause.message.contains 'Unexpected statement' } def 'should error due to missing branch expression' () { when: - dsl_eval(''' - Channel.empty() .branch { def x=1 } + runScript(''' + channel.empty() .branch { def x=1 } ''') then: def e = thrown(ScriptCompilationException) - e.message.contains 'Branch criteria should declare at least one branch' + e.cause.message.contains 'Branch criteria should declare at least one branch' when: - dsl_eval(''' - Channel.empty() .branch { } + runScript(''' + channel.empty() .branch { } ''') then: def e2 = thrown(ScriptCompilationException) - e2.message.contains 'Branch criteria should declare at least one branch' + e2.cause.message.contains 'Branch criteria should declare at least one branch' } def 'should error due to missing expression stmt' () { when: - dsl_eval(''' - Channel.empty() .branch { foo: true; if(x) {} } + runScript(''' + channel.empty() .branch { foo: true; if(true) {} } ''') then: def e = thrown(ScriptCompilationException) - e.message.contains 'Unexpected statement in branch condition' + e.cause.message.contains 'Unexpected statement in branch condition' } @@ -350,9 +327,8 @@ class BranchOpTest extends Dsl2Spec { def 'should branch value ch' () { when: - def result = dsl_eval(''' - Channel - .value(10) + def result = runScript(''' + channel.value(10) .branch { foo: it <5 otherwise: it diff --git a/modules/nextflow/src/test/groovy/nextflow/extension/ConcatOpTest.groovy b/modules/nextflow/src/test/groovy/nextflow/extension/ConcatOpTest.groovy index 5a40b18927..5ed9fea35d 100644 --- a/modules/nextflow/src/test/groovy/nextflow/extension/ConcatOpTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/extension/ConcatOpTest.groovy @@ -16,23 +16,24 @@ package nextflow.extension -import spock.lang.Timeout - import nextflow.Channel +import spock.lang.Timeout import test.Dsl2Spec + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso */ @Timeout(5) -class ConcatOp2Test extends Dsl2Spec { +class ConcatOpTest extends Dsl2Spec { def 'should concat two channel'() { when: - def result = dsl_eval(''' - c1 = Channel.of(1,2,3) - c2 = Channel.of('a','b','c') + def result = runScript(''' + c1 = channel.of(1,2,3) + c2 = channel.of('a','b','c') c1.concat(c2) ''') then: @@ -47,10 +48,10 @@ class ConcatOp2Test extends Dsl2Spec { def 'should concat value with channel'() { when: - def result = dsl_eval(''' - ch1 = Channel.value(1) - ch2 = Channel.of(2,3) - ch1.concat(ch2) + def result = runScript(''' + ch1 = channel.value(1) + ch2 = channel.of(2,3) + ch1.concat(ch2) ''') then: result.val == 1 @@ -61,10 +62,10 @@ class ConcatOp2Test extends Dsl2Spec { def 'should concat two value channels'() { when: - def result = dsl_eval(''' - ch1 = Channel.value(1) - ch2 = Channel.value(2) - ch1.concat(ch2) + def result = runScript(''' + ch1 = channel.value(1) + ch2 = channel.value(2) + ch1.concat(ch2) ''') then: result.val == 1 @@ -74,20 +75,20 @@ class ConcatOp2Test extends Dsl2Spec { def 'should concat with empty'() { when: - def result = dsl_eval(''' - ch1 = Channel.value(1) - ch2 = Channel.empty() - ch1.concat(ch2) + def result = runScript(''' + ch1 = channel.value(1) + ch2 = channel.empty() + ch1.concat(ch2) ''') then: result.val == 1 result.val == Channel.STOP - + when: - result = dsl_eval(''' - ch1 = Channel.empty() - ch2 = Channel.empty() - ch1.concat(ch2) + result = runScript(''' + ch1 = channel.empty() + ch2 = channel.empty() + ch1.concat(ch2) ''') then: result.val == Channel.STOP diff --git a/modules/nextflow/src/test/groovy/nextflow/extension/MixOpTest.groovy b/modules/nextflow/src/test/groovy/nextflow/extension/MixOpTest.groovy index b91765d6da..08551654c7 100644 --- a/modules/nextflow/src/test/groovy/nextflow/extension/MixOpTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/extension/MixOpTest.groovy @@ -17,8 +17,9 @@ package nextflow.extension import spock.lang.Timeout - import test.Dsl2Spec + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso @@ -28,13 +29,12 @@ class MixOpTest extends Dsl2Spec { def 'should mix channels'() { when: - def result = dsl_eval(''' - c1 = Channel.of( 1,2,3 ) - c2 = Channel.of( 'a','b' ) - c3 = Channel.value( 'z' ) - c1.mix(c2,c3) - - ''') .toList().val + def result = runScript(''' + c1 = channel.of( 1,2,3 ) + c2 = channel.of( 'a','b' ) + c3 = channel.value( 'z' ) + c1.mix(c2, c3).collect() + ''').val then: 1 in result @@ -49,20 +49,20 @@ class MixOpTest extends Dsl2Spec { def 'should mix with value channels'() { when: - def result = dsl_eval(''' - Channel.value(1).mix( Channel.fromList([2,3]) ) + def result = runScript(''' + channel.value(1).mix( channel.of(2,3) ).collect() ''') then: - result.toList().val.sort() == [1,2,3] + result.val.sort() == [1,2,3] } def 'should mix with two singleton'() { when: - def result = dsl_eval(''' - Channel.value(1).mix( Channel.value(2) ) + def result = runScript(''' + channel.value(1).mix( channel.value(2) ).collect() ''') then: - result.toList().val.sort() == [1,2] + result.val.sort() == [1,2] } } diff --git a/modules/nextflow/src/test/groovy/nextflow/extension/MultiMapOpTest.groovy b/modules/nextflow/src/test/groovy/nextflow/extension/MultiMapOpTest.groovy index 75dbb611f2..b737beb363 100644 --- a/modules/nextflow/src/test/groovy/nextflow/extension/MultiMapOpTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/extension/MultiMapOpTest.groovy @@ -18,15 +18,18 @@ package nextflow.extension import groovyx.gpars.dataflow.DataflowVariable import org.junit.Rule - import nextflow.Channel +import spock.lang.Timeout import test.Dsl2Spec import test.OutputCapture +import static test.ScriptHelper.* + /** * * @author Paolo Di Tommaso */ +@Timeout(5) class MultiMapOpTest extends Dsl2Spec { @Rule @@ -35,9 +38,8 @@ class MultiMapOpTest extends Dsl2Spec { def 'should fork channel' () { when: - def result = dsl_eval(''' - Channel - .from(0,1,2) + def result = runScript(''' + channel.of(0,1,2) .multiMap { foo: it+1 bar: it*it+2 @@ -67,9 +69,8 @@ class MultiMapOpTest extends Dsl2Spec { def 'should fork channel with custom param' () { when: - def result = dsl_eval(''' - Channel - .from(0,1,2) + def result = runScript(''' + channel.of(0,1,2) .multiMap { p -> foo: p+1 bar: p*p+2 @@ -98,13 +99,13 @@ class MultiMapOpTest extends Dsl2Spec { def 'should pass criteria as argument' () { when: - dsl_eval(''' + runScript(''' criteria = multiMapCriteria { foo: it bar: it*it } - ch1 = Channel.of(1,2,3).multiMap(criteria) + ch1 = channel.of(1,2,3).multiMap(criteria) ch1.foo.view { "foo:$it" } ch1.bar.view { "bar:$it" } @@ -124,9 +125,8 @@ class MultiMapOpTest extends Dsl2Spec { def 'should fork channel value ch' () { when: - def result = dsl_eval(''' - Channel - .value('hello') + def result = runScript(''' + channel.value('hello') .multiMap { p -> foo: p.toUpperCase() bar: p.reverse() diff --git a/modules/nextflow/src/test/groovy/nextflow/extension/SetOpTest.groovy b/modules/nextflow/src/test/groovy/nextflow/extension/SetOpTest.groovy index 3620277fb6..07bb7079b1 100644 --- a/modules/nextflow/src/test/groovy/nextflow/extension/SetOpTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/extension/SetOpTest.groovy @@ -17,8 +17,9 @@ package nextflow.extension import spock.lang.Timeout - import test.Dsl2Spec + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso @@ -28,68 +29,42 @@ class SetOpTest extends Dsl2Spec { def 'should set a channel in the global context' () { when: - def result = dsl_eval(/ - Channel.of(1,2,3) | set { foo } - foo | map { it *2 } - /) + def result = runScript(''' + channel.of(1,2,3) | set { foo } + foo | map { it * 2 } + ''') then: result.val == 2 result.val == 4 result.val == 6 when: - result = dsl_eval(/ - Channel.value(5) | set { foo } - foo | map { it *2 } - /) + result = runScript(''' + channel.value(5) | set { foo } + foo | map { it * 2 } + ''') then: result.val == 10 } def 'should invoke set with dot notation' () { when: - def result = dsl_eval(/ - Channel.of(1,2,3).set { foo } - foo.map { it *2 } - /) + def result = runScript(''' + channel.of(1,2,3).set { foo } + foo.map { it * 2 } + ''') then: result.val == 2 result.val == 4 result.val == 6 when: - result = dsl_eval(/ - Channel.value('hello').set { foo } + result = runScript(''' + channel.value('hello').set { foo } foo.map { it.toUpperCase() } - /) + ''') then: result.val == 'HELLO' } - - def 'should assign multiple channels in the current binding' () { - when: - def result = dsl_eval(/ - def ch1 = Channel.value('X') - def ch2 = Channel.value('Y') - - new nextflow.script.ChannelOut([ch1]) .set { foo } - return foo - /) - then: - result.val == 'X' - - when: - result = dsl_eval(/ - def ch1 = Channel.value('X') - def ch2 = Channel.value('Y') - - new nextflow.script.ChannelOut([ch1, ch2]) .set { bar } - return bar - /) - then: - result[0].val == 'X' - result[1].val == 'Y' - } - } diff --git a/modules/nextflow/src/test/groovy/nextflow/extension/SplitFastqOp2Test.groovy b/modules/nextflow/src/test/groovy/nextflow/extension/SplitFastqOp2Test.groovy deleted file mode 100644 index 3391b222f1..0000000000 --- a/modules/nextflow/src/test/groovy/nextflow/extension/SplitFastqOp2Test.groovy +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright 2013-2024, Seqera Labs - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package nextflow.extension - -import java.nio.file.Files - -import nextflow.Channel -import test.Dsl2Spec -/** - * - * @author Paolo Di Tommaso - */ -class SplitFastqOp2Test extends Dsl2Spec { - - String READS = ''' - @SRR636272.19519409/1 - GGCCCGGCAGCAGGATGATGCTCTCCCGGGCCAAGCCGGCTGTGGGGAGCACCCCGCCGCAGGGGGACAGGCGGAGGAAGAAAGGGAAGAAGGTGCCACAGATCG - + - CCCFFFFDHHD;FF=GGDHGGHIIIGHIIIBDGBFCAHG@E=6?CBDBB;?BB@BD8BB;BDB<>>;@?BB<9>&5ACBB8ACDCD@CD> - @SRR636272.21107783/1 - CGGGGAGCGCGGGCCCGGCAGCAGGATGATGCTCTCCCGGGCCAAGCCGGCTGTAGGGAGCACCCCGCCGCAGGGGGACAGGCGAGATCGGAAGAGCACACGTCT - + - BCCFFDFFHHHHHJJJJJIJHHHHFFFFEEEEEEEDDDDDDBDBDBBDBBDBBB(:ABCDDDDDDDDDDDDDDDD@BBBDDDDDDDDDDDDBDDDDDDDDDDADC - @SRR636272.23331539/1 - GGAGCACCCCGCCGCAGGGGGACAGGCGGAGGAAGAAAGGGAAGAAGGTGCCACAGCTGGAGGAGCTGCTGGCCGGGAGGGACTTCACCGGCGAGATCGGAAGAG - + - CCCFFFFFHHHHHJJJJJJJJJJJJJJJHFDDBDDBDDDDDDDDDDDDADDDDDDDDDDDDDDDDDDDDDDDDDDBDBDDD9@DDDDDDDDDDDDBBDDDBDD@@ - '''.stripIndent().leftTrim() - - String READS2 = ''' - @SRR636272.19519409/2 - GGCCCGGCAGCAGGATGATGCTCTCCCGGGCCAAGCCGGCTGTGGGGAGCACCCCGCCGCAGGGGGACAGGCGGAGGAAGAAAGGGAAGAAGGTGCCACAGATCG - + - CCCFFFFDHHD;FF=GGDHGGHIIIGHIIIBDGBFCAHG@E=6?CBDBB;?BB@BD8BB;BDB<>>;@?BB<9>&5ACBB8ACDCD@CD> - @SRR636272.21107783/2 - CGGGGAGCGCGGGCCCGGCAGCAGGATGATGCTCTCCCGGGCCAAGCCGGCTGTAGGGAGCACCCCGCCGCAGGGGGACAGGCGAGATCGGAAGAGCACACGTCT - + - BCCFFDFFHHHHHJJJJJIJHHHHFFFFEEEEEEEDDDDDDBDBDBBDBBDBBB(:ABCDDDDDDDDDDDDDDDD@BBBDDDDDDDDDDDDBDDDDDDDDDDADC - @SRR636272.23331539/2 - GGAGCACCCCGCCGCAGGGGGACAGGCGGAGGAAGAAAGGGAAGAAGGTGCCACAGCTGGAGGAGCTGCTGGCCGGGAGGGACTTCACCGGCGAGATCGGAAGAG - + - CCCFFFFFHHHHHJJJJJJJJJJJJJJJHFDDBDDBDDDDDDDDDDDDADDDDDDDDDDDDDDDDDDDDDDDDDDBDBDDD9@DDDDDDDDDDDDBBDDDBDD@@ - '''.stripIndent().leftTrim() - - - def 'should split pair-ended using dsl2' () { - given: - def folder = Files.createTempDirectory('test') - def file1 = folder.resolve('one.fq'); file1.text = READS - def file2 = folder.resolve('two.fq'); file2.text = READS2 - - def result - def channel - - when: - channel = dsl_eval(""" - Channel.of(['sample_id', file("$file1"), file("$file2")]).splitFastq(by:1, pe:true) - """) - - result = channel.val - - then: - result[0] == 'sample_id' - result[1] == ''' - @SRR636272.19519409/1 - GGCCCGGCAGCAGGATGATGCTCTCCCGGGCCAAGCCGGCTGTGGGGAGCACCCCGCCGCAGGGGGACAGGCGGAGGAAGAAAGGGAAGAAGGTGCCACAGATCG - + - CCCFFFFDHHD;FF=GGDHGGHIIIGHIIIBDGBFCAHG@E=6?CBDBB;?BB@BD8BB;BDB<>>;@?BB<9>&5>;@?BB<9>&5ACBB8ACDCD@CD> - '''.stripIndent().leftTrim() - result[2] == ''' - @SRR636272.13995011/2 - GCAGGATGATGCTCTCCCGGGCCAAGCCGGCTGTGGGGAGCACCCCGCCGCAGGGGGACAGGCGGAGGAAGAAAGGGAGATCGGAAGAGCACACGTCTGAACTCC - + - BBCFDFDEFFHHFIJIHGHGHGIIFIJJJJIGGBFHHIEGBEFEFFCDDDD:@@ACBB8ACDCD@CD> - '''.stripIndent().leftTrim() - - when: - result = channel.val - then: - result[0] == 'sample_id' - result[1] == ''' - @SRR636272.21107783/1 - CGGGGAGCGCGGGCCCGGCAGCAGGATGATGCTCTCCCGGGCCAAGCCGGCTGTAGGGAGCACCCCGCCGCAGGGGGACAGGCGAGATCGGAAGAGCACACGTCT - + - BCCFFDFFHHHHHJJJJJIJHHHHFFFFEEEEEEEDDDDDDBDBDBBDBBDBBB(:ABCDDDDDDDDDDDDDDDD@BBBDDDDDDDDDDDDBDDDDDDDDDDADC - '''.stripIndent().leftTrim() - result[2] == ''' - @SRR636272.21107783/2 - CGGGGAGCGCGGGCCCGGCAGCAGGATGATGCTCTCCCGGGCCAAGCCGGCTGTAGGGAGCACCCCGCCGCAGGGGGACAGGCGAGATCGGAAGAGCACACGTCT - + - BCCFFDFFHHHHHJJJJJIJHHHHFFFFEEEEEEEDDDDDDBDBDBBDBBDBBB(:ABCDDDDDDDDDDDDDDDD@BBBDDDDDDDDDDDDBDDDDDDDDDDADC - '''.stripIndent().leftTrim() - - when: - result = channel.val - then: - result[0] == 'sample_id' - result[1] == ''' - @SRR636272.23331539/1 - GGAGCACCCCGCCGCAGGGGGACAGGCGGAGGAAGAAAGGGAAGAAGGTGCCACAGCTGGAGGAGCTGCTGGCCGGGAGGGACTTCACCGGCGAGATCGGAAGAG - + - CCCFFFFFHHHHHJJJJJJJJJJJJJJJHFDDBDDBDDDDDDDDDDDDADDDDDDDDDDDDDDDDDDDDDDDDDDBDBDDD9@DDDDDDDDDDDDBBDDDBDD@@ - '''.stripIndent().leftTrim() - result[2] == ''' - @SRR636272.23331539/2 - GGAGCACCCCGCCGCAGGGGGACAGGCGGAGGAAGAAAGGGAAGAAGGTGCCACAGCTGGAGGAGCTGCTGGCCGGGAGGGACTTCACCGGCGAGATCGGAAGAG - + - CCCFFFFFHHHHHJJJJJJJJJJJJJJJHFDDBDDBDDDDDDDDDDDDADDDDDDDDDDDDDDDDDDDDDDDDDDBDBDDD9@DDDDDDDDDDDDBBDDDBDD@@ - '''.stripIndent().leftTrim() - - when: - result = channel.val - then: - result == Channel.STOP - - cleanup: - folder?.deleteDir() - - } -} diff --git a/modules/nextflow/src/test/groovy/nextflow/extension/OperatorDsl2Test.groovy b/modules/nextflow/src/test/groovy/nextflow/extension/UniqueOpTest.groovy similarity index 80% rename from modules/nextflow/src/test/groovy/nextflow/extension/OperatorDsl2Test.groovy rename to modules/nextflow/src/test/groovy/nextflow/extension/UniqueOpTest.groovy index 47caf5abe6..8421db674e 100644 --- a/modules/nextflow/src/test/groovy/nextflow/extension/OperatorDsl2Test.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/extension/UniqueOpTest.groovy @@ -21,17 +21,19 @@ import nextflow.Channel import spock.lang.Timeout import test.Dsl2Spec +import static test.ScriptHelper.* + /** * * @author Paolo Di Tommaso */ -@Timeout(10) -class OperatorDsl2Test extends Dsl2Spec { +@Timeout(5) +class UniqueOpTest extends Dsl2Spec { def 'should test unique' () { when: - def channel = dsl_eval(""" - Channel.of(1,2,3).unique() + def channel = runScript(""" + channel.of(1,2,3).unique() """) then: channel.val == 1 @@ -42,8 +44,8 @@ class OperatorDsl2Test extends Dsl2Spec { def 'should test unique with value' () { when: - def channel = dsl_eval(""" - Channel.value(1).unique() + def channel = runScript(""" + channel.value(1).unique() """) then: channel.val == 1 @@ -51,11 +53,10 @@ class OperatorDsl2Test extends Dsl2Spec { def 'should test unique with collect' () { when: - def ch = dsl_eval(""" - Channel.of( 'a', 'b', 'c') + def ch = runScript(""" + channel.of( 'a', 'b', 'c') .collect() .unique() - .view() """) then: ch.val == ['a','b','c'] diff --git a/modules/nextflow/src/test/groovy/nextflow/extension/plugin/ChannelFactoryInstanceTest.groovy b/modules/nextflow/src/test/groovy/nextflow/extension/plugin/ChannelFactoryInstanceTest.groovy index ea2fb42193..9694f80e5b 100644 --- a/modules/nextflow/src/test/groovy/nextflow/extension/plugin/ChannelFactoryInstanceTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/extension/plugin/ChannelFactoryInstanceTest.groovy @@ -28,7 +28,8 @@ import nextflow.extension.CH import nextflow.plugin.extension.PluginExtensionPoint import nextflow.plugin.extension.PluginExtensionProvider import spock.lang.Specification -import test.MockScriptRunner + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso @@ -91,12 +92,11 @@ class ChannelFactoryInstanceTest extends Specification { .loadPluginExtensionMethods("nf-foo",ext1, ['alpha':'alpha']) and: def SCRIPT = ''' - Channel.alpha(['one','two','three']) + channel.alpha(['one','two','three']) ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 'one' result.val == 'two' @@ -125,8 +125,7 @@ class ChannelFactoryInstanceTest extends Specification { ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 'one' result.val == 'two' @@ -155,28 +154,25 @@ class ChannelFactoryInstanceTest extends Specification { .loadPluginExtensionMethods("nf-foo",ext2, ['omega':'omega']) and: def SCRIPT = ''' - def ch1 = channel.alpha([1,2,3]) - def ch2 = channel.omega(['X','Y','Z']) - process sayHello { input: val x val y - output: + output: val z exec: z = "$x $y" } - + workflow { - main: sayHello(ch1, ch2) - emit: sayHello.out.toSortedList() + def ch1 = channel.alpha([1,2,3]) + def ch2 = channel.omega(['X','Y','Z']) + sayHello(ch1, ch2).toSortedList() } ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == ['1 X', '2 Y', '3 Z'] @@ -206,8 +202,7 @@ class ChannelFactoryInstanceTest extends Specification { ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 2 result.val == 3 diff --git a/modules/nextflow/src/test/groovy/nextflow/script/BaseScriptTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/BaseScriptTest.groovy index 6db6f66aed..eda32282cc 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/BaseScriptTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/BaseScriptTest.groovy @@ -23,9 +23,11 @@ import nextflow.NextflowMeta import nextflow.Session import nextflow.SysEnv import nextflow.extension.FilesEx +import nextflow.script.ScriptBinding import nextflow.secret.SecretsLoader import test.Dsl2Spec -import test.TestHelper + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso @@ -36,21 +38,12 @@ class BaseScriptTest extends Dsl2Spec { def 'should define implicit variables' () { given: - def script = Files.createTempFile('test',null) - and: - def WORKFLOW = Mock(WorkflowMetadata) - def WORK_DIR = Paths.get('/work/dir') - def PROJECT_DIR = Paths.get('/some/base') - and: - def session = Mock(Session) { - getBaseDir() >> PROJECT_DIR - getWorkDir() >> WORK_DIR - getWorkflowMetadata() >> WORKFLOW - } - def binding = new ScriptBinding([:]) - def parser = ScriptLoaderFactory.create(session) + def folder = Files.createTempDirectory('test') + def WORK_DIR = folder.resolve('work') when: + def config = [workDir: WORK_DIR] + def script = folder.resolve('main.nf') script.text = ''' result = [:] result.baseDir = baseDir @@ -60,94 +53,24 @@ class BaseScriptTest extends Dsl2Spec { result.workflow = workflow result.launchDir = launchDir result.moduleDir = moduleDir + result ''' - parser.setBinding(binding) - parser.runScript(script) - - then: - binding.result.baseDir == PROJECT_DIR - binding.result.projectDir == PROJECT_DIR - binding.result.workDir == WORK_DIR - binding.result.launchDir == Paths.get('.').toRealPath() - binding.result.moduleDir == script.parent - binding.workflow == WORKFLOW - binding.nextflow == NextflowMeta.instance - - cleanup: - script?.delete() - } - - def 'should use custom entry workflow' () { - - given: - def script = Files.createTempFile('test',null) - and: - def session = Mock(Session) { - getConfig() >> [:] - } - def binding = new ScriptBinding([:]) - def parser = ScriptLoaderFactory.create(session) - - when: - script.text = ''' - workflow foo { - } - - workflow { - error 'you were supposed to run foo!' - } - ''' - - parser.setBinding(binding) - parser.setEntryName('foo') - parser.runScript(script) + def result = runScript(script, config: config) then: - noExceptionThrown() + result.baseDir == script.parent + result.projectDir == script.parent + result.workDir == WORK_DIR + result.launchDir == Paths.get('.').toRealPath() + result.moduleDir == script.parent + result.workflow instanceof WorkflowMetadata + result.nextflow == NextflowMeta.instance cleanup: script?.delete() } - def 'should use entry workflow from module' () { - - given: - def folder = TestHelper.createInMemTempDir() - def module = folder.resolve('module.nf') - def script = folder.resolve('main.nf') - and: - def session = Mock(Session) { - getConfig() >> [:] - } - def binding = new ScriptBinding([:]) - def parser = ScriptLoaderFactory.create(session) - - when: - module.text = ''' - workflow foo { - } - ''' - - script.text = ''' - include { foo } from './module.nf' - - workflow { - error 'you were supposed to run foo!' - } - ''' - - parser.setBinding(binding) - parser.setEntryName('foo') - parser.runScript(script) - - then: - noExceptionThrown() - - cleanup: - folder?.delete() - } - def 'should resolve secret in a script' () { given: SecretsLoader.instance.reset() @@ -167,20 +90,13 @@ class BaseScriptTest extends Dsl2Spec { and: FilesEx.setPermissions(secrets, 'rw-------') SysEnv.push(NXF_SECRETS_FILE:secrets.toAbsolutePath().toString()) - and: - def session = Mock(Session) - def binding = new ScriptBinding([:]) - def parser = ScriptLoaderFactory.create(session) when: script.text = ''' - return secrets.FOO + secrets.FOO ''' - def result = parser - .setBinding(binding) - .runScript(script) - .getResult() + def result = runScript(script) then: result == 'ciao' diff --git a/modules/nextflow/src/test/groovy/nextflow/script/BodyDefTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/BodyDefTest.groovy index 683f77f122..89d2edbec1 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/BodyDefTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/BodyDefTest.groovy @@ -16,12 +16,8 @@ package nextflow.script -import static test.TestParser.* - -import nextflow.processor.TaskProcessor import spock.lang.Timeout import test.Dsl2Spec -import test.MockScriptRunner /** * @@ -62,81 +58,4 @@ class BodyDefTest extends Dsl2Spec { body.getValNames() == [] as Set } - def 'should return variable names referenced in task body'( ) { - - setup: - def text = ''' - - String x = 1 - - @Field - String y = 'Ciao' - - z = 'str' - - process hola { - - / - println $x + $y + $z - / - } - - workflow { hola() } - ''' - when: - def process = parseAndReturnProcess(text) - then: - process.taskBody.valRefs == [ - new TokenValRef('x', 13, 20), - new TokenValRef('y', 13, 25), - new TokenValRef('z', 13, 30) ] as Set - - process.taskBody.getValNames() == ['x','y','z'] as Set - } - - def 'should return property names referenced in task body'() { - - given: - def script = - ''' - class Foo { def foo() { return [x:1] }; } - - alpha = 1 - params.beta = 2 - params.zeta = new Foo() - params.gamma = [:] - params.hola = 'Ciao' - delta = new Foo() - x = 'alpha' - - process simpleTask { - input: - val x - - """ - echo ${alpha} - echo ${params.beta} - echo ${params?.gamma?.omega} - echo ${params.zeta.foo()} - echo ${params.zeta.foo().x} - echo ${delta.foo().x} - echo ${params."$x"} - """ - } - - workflow { - simpleTask('hola') - } - ''' - and: - def config = [process: [executor:'nope']] - - when: - new MockScriptRunner(config).setScript(script).execute() - def processor = TaskProcessor.currentProcessor() - then: - processor.getTaskBody().getValNames() == ['alpha', 'params.beta', 'params.gamma.omega', 'params.zeta', 'delta', 'params', 'x'] as Set - - } - } diff --git a/modules/nextflow/src/test/groovy/nextflow/script/ScriptDslTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/ScriptDslTest.groovy index b164f0d995..7546a425fc 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/ScriptDslTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/ScriptDslTest.groovy @@ -5,36 +5,36 @@ import nextflow.exception.MissingProcessException import nextflow.exception.ScriptCompilationException import nextflow.exception.ScriptRuntimeException import test.Dsl2Spec -import test.MockScriptRunner + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso */ class ScriptDslTest extends Dsl2Spec { - def 'should define a process with output alias' () { given: def SCRIPT = ''' - + process foo { - output: val x, emit: 'ch1' - output: val y, emit: 'ch2' - exec: x = 'Hello'; y = 'world' + output: + val x, emit: ch1 + val y, emit: ch2 + + exec: + x = 'Hello' + y = 'world' } - + workflow { - main: - foo() - emit: - foo.out.ch1 - foo.out.ch2 + foo() + [ foo.out.ch1, foo.out.ch2 ] } ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result[0].val == 'Hello' result[1].val == 'world' @@ -42,26 +42,15 @@ class ScriptDslTest extends Dsl2Spec { def 'should execute basic workflow' () { when: - def result = dsl_eval ''' - - workflow { - emit: result - main: + def result = runScript ''' + + workflow hello { + emit: result = 'Hello world' } - ''' - - then: - result.val == 'Hello world' - } - def 'should execute emit' () { - when: - def result = dsl_eval ''' - workflow { - emit: - result = 'Hello world' + hello() } ''' @@ -71,14 +60,18 @@ class ScriptDslTest extends Dsl2Spec { def 'should emit expression' () { when: - def result = dsl_eval ''' - - def foo() { 'Hello world' } - - workflow { + def result = runScript ''' + + def foo() { 'Hello world' } + + workflow foo_upper { emit: foo().toUpperCase() } + + workflow { + foo_upper() + } ''' then: @@ -87,17 +80,21 @@ class ScriptDslTest extends Dsl2Spec { def 'should emit process out' () { when: - def result = dsl_eval ''' - + def result = runScript ''' + process foo { - output: val x + output: val x exec: x = 'Hello' } - - workflow { + + workflow foo_out { main: foo() emit: foo.out } + + workflow { + foo_out() + } ''' then: @@ -107,37 +104,35 @@ class ScriptDslTest extends Dsl2Spec { def 'should define processes and workflow' () { when: - def result = dsl_eval ''' + def result = runScript ''' process foo { - input: val data - output: val result - exec: + input: val data + output: val result + exec: result = "$data mundo" - } - + } + process bar { - input: val data + input: val data output: val result - exec: - result = data.toUpperCase() - } - + exec: + result = data.toUpperCase() + } + workflow alpha { - take: - data - + take: + data + main: - foo(data) - bar(foo.out) - - emit: - x = bar.out - - } - + foo(data) + bar(foo.out) + + emit: + x = bar.out + } + workflow { - main: alpha('Hello') - emit: x = alpha.out + alpha('Hello') } ''' @@ -146,88 +141,82 @@ class ScriptDslTest extends Dsl2Spec { } - def 'should access nextflow enabling property' () { - when: - def result = dsl_eval ''' - return nextflow.enable.dsl - ''' - - then: - result == 2 - } - - def 'should not allow function with reserved identifier' () { when: - dsl_eval """ + runScript """ def main() { println 'ciao' } """ then: def err = thrown(ScriptCompilationException) - err.message.contains('Identifier `main` is reserved for internal use') + err.cause.message.contains('`main` is not allowed as a function name because it is reserved for internal use') } def 'should not allow process with reserved identifier' () { when: - dsl_eval """ + runScript """ process main { + script: /echo ciao/ } + + workflow {} """ then: def err = thrown(ScriptCompilationException) - err.message.contains('Identifier `main` is reserved for internal use') + err.cause.message.contains('`main` is not allowed as a process name because it is reserved for internal use') } def 'should not allow workflow with reserved identifier' () { when: - dsl_eval """ + runScript """ workflow main { /echo ciao/ } + + workflow {} """ then: def err = thrown(ScriptCompilationException) - err.message.contains('Identifier `main` is reserved for internal use') + err.cause.message.contains('`main` is not allowed as a workflow name because it is reserved for internal use') } def 'should not allow duplicate workflow keyword' () { when: - dsl_eval( - """ - workflow { - /echo ciao/ - } - - workflow { - /echo miao/ - } + runScript( + """ + workflow { + /echo ciao/ + } + + workflow { + /echo miao/ + } """ ) then: def err = thrown(ScriptCompilationException) - err.message.contains('Duplicate entry workflow definition') + err.cause.message.contains('Entry workflow defined more than once') } def 'should apply operator to process result' () { when: - def result = dsl_eval(/ + def result = runScript(''' process hello { output: val result exec: result = "Hello" - } - + } + workflow { - main: hello() - emit: hello.out.map { it.toUpperCase() } + hello() + hello.out.map { it.toUpperCase() } } - /) + ''') then: result.val == 'HELLO' } @@ -235,20 +224,19 @@ class ScriptDslTest extends Dsl2Spec { def 'should branch and view' () { when: - def result = dsl_eval(/ - Channel - .from(1,2,3,40,50) - .branch { - small: it < 10 - large: it > 10 + def result = runScript(''' + channel.of(1,2,3,40,50) + .branch { + small: it < 10 + large: it > 10 } .set { result } - - ch1 = result.small.map { it } - ch2 = result.large.map { it } - [ch1, ch2] - /) + ch1 = result.small.map { it } + ch2 = result.large.map { it } + + [ch1, ch2] + ''') then: result[0].val == 1 result[0].val == 2 @@ -261,19 +249,19 @@ class ScriptDslTest extends Dsl2Spec { def 'should allow pipe process and operator' () { when: - def result = dsl_eval(''' + def result = runScript(''' process foo { output: val result exec: result = "hello" - } - + } + process bar { output: val result exec: result = "world" - } - + } + workflow { - emit: (foo & bar) | concat + (foo & bar) | concat } ''') @@ -285,72 +273,45 @@ class ScriptDslTest extends Dsl2Spec { def 'should allow process and operator composition' () { when: - def result = dsl_eval(''' + def result = runScript(''' process foo { output: val result exec: result = "hello" - } - - process bar { - output: val result - exec: result = "world" - } - - workflow { - main: foo(); bar() - emit: foo.out.concat(bar.out) } - ''') - - then: - result.val == 'hello' - result.val == 'world' - result.val == Channel.STOP - } - - def 'should run entry flow' () { - when: - def result = dsl_eval('TEST_FLOW', ''' - process foo { - output: val result - exec: result = "hello" - } - process bar { output: val result exec: result = "world" - } - - workflow { - main: foo() - emit: foo.out } - - workflow TEST_FLOW { - main: bar() - emit: bar.out + + workflow { + foo() + bar() + foo.out.concat(bar.out) } ''') then: + result.val == 'hello' result.val == 'world' - + result.val == Channel.STOP } - def 'should not allow composition' () { + def 'should not allow invalid composition' () { when: - dsl_eval(''' + runScript(''' process foo { + script: /echo foo/ } - + process bar { - input: val x - /echo bar $x/ + input: val x + script: + "echo bar $x" } - + workflow { bar(foo()) } @@ -364,16 +325,18 @@ class ScriptDslTest extends Dsl2Spec { def 'should report error accessing undefined out/a' () { when: - dsl_eval(''' + runScript(''' process foo { + script: /echo foo/ } - + process bar { - input: val x - /echo bar $x/ + input: val x + script: + "echo bar $x" } - + workflow { bar(foo.out) } @@ -386,16 +349,18 @@ class ScriptDslTest extends Dsl2Spec { def 'should report error accessing undefined out/b' () { when: - dsl_eval(''' + runScript(''' process foo { + script: /echo foo/ } - + process bar { - input: val x - /echo bar $x/ + input: val x + script: + "echo bar $x" } - + workflow { bar(foo.out) } @@ -408,15 +373,16 @@ class ScriptDslTest extends Dsl2Spec { def 'should report error accessing undefined out/c' () { when: - dsl_eval(''' + runScript(''' process foo { + script: /echo foo/ } - + workflow flow1 { foo() } - + workflow { flow1() flow1.out.view() @@ -430,20 +396,22 @@ class ScriptDslTest extends Dsl2Spec { def 'should report error accessing undefined out/d' () { when: - dsl_eval(''' + runScript(''' process foo { + script: /echo foo/ } - + process bar { - input: val x - /echo bar $x/ + input: val x + script: + "echo bar $x" } - + workflow flow1 { foo() } - + workflow { flow1 | bar } @@ -456,15 +424,16 @@ class ScriptDslTest extends Dsl2Spec { def 'should report error accessing undefined out/e' () { when: - dsl_eval(''' + runScript(''' process foo { + script: /echo foo/ } - + workflow flow1 { foo() } - + workflow { flow1.out.view() } @@ -477,13 +446,14 @@ class ScriptDslTest extends Dsl2Spec { def 'should fail with wrong scope'() { when: - dsl_eval('''\ + runScript('''\ process foo { + script: /echo foo/ } - - workflow { - main: + + workflow foo_flow { + main: flow() emmit: flow.out @@ -492,13 +462,13 @@ class ScriptDslTest extends Dsl2Spec { then: def err = thrown(ScriptCompilationException) - err.message.contains "Unknown execution scope 'emmit:' -- Did you mean 'emit'" + err.cause.message.contains "Invalid workflow definition -- check for missing or out-of-order section labels" } def 'should fail because process is not defined'() { when: - dsl_eval( + runScript( ''' process sleeper { exec: @@ -506,97 +476,90 @@ class ScriptDslTest extends Dsl2Spec { sleep 5 """ } - + workflow { main: - sleeper() - hello() + sleeper() + hello() } - + ''') then: - def err = thrown(MissingProcessException) - err.message == "Missing process or function hello()" + def err = thrown(ScriptCompilationException) + err.cause.message.contains '`hello` is not defined' } def 'should fail because is not defined /2' () { when: - dsl_eval(''' + runScript(''' process sleeper { exec: """ sleep 5 """ } - + workflow nested { main: - sleeper() - sleeper_2() + sleeper() + sleeper_2() } - - workflow{ + + workflow { nested() } ''') then: - def err = thrown(MissingProcessException) - err.message == "Missing process or function sleeper_2() -- Did you mean 'sleeper' instead?" + def err = thrown(ScriptCompilationException) + err.cause.message.contains '`sleeper_2` is not defined' } def 'should fail because is not defined /3' () { when: - dsl_eval(''' + runScript(''' process sleeper1 { + script: /echo 1/ } - + process sleeper2 { + script: /echo 3/ } - + workflow nested { main: - sleeper1() - sleeper3() + sleeper1() + sleeper3() } - - workflow{ + + workflow { nested() } ''') then: - def err = thrown(MissingProcessException) - err.message == '''\ - Missing process or function sleeper3() - - Did you mean any of these instead? - sleeper1 - sleeper2 - '''.stripIndent() + def err = thrown(ScriptCompilationException) + err.cause.message.contains '`sleeper3` is not defined' } def 'should not conflict with private meta attribute' () { when: - def result = dsl_eval ''' - + def result = runScript ''' + process foo { input: val x - output: val y + output: val y exec: y = x } - + workflow { - main: - meta = channel.of('Hello') - foo(meta) - emit: - foo.out + meta = channel.of('Hello') + foo(meta) } ''' @@ -608,15 +571,15 @@ class ScriptDslTest extends Dsl2Spec { def 'should throw an exception on missing method' () { when: - dsl_eval ''' + runScript ''' Channel.doesNotExist() ''' then: - def e1 = thrown(MissingMethodException) - e1.message == 'No signature of method: java.lang.Object.Channel.doesNotExist() is applicable for argument types: () values: []' + def e1 = thrown(MissingProcessException) + e1.message == 'Missing process or function Channel.doesNotExist()' when: - dsl_eval ''' + runScript ''' workflow { Channel.doesNotExist() } @@ -626,22 +589,4 @@ class ScriptDslTest extends Dsl2Spec { e2.message == 'Missing process or function Channel.doesNotExist()' } - def 'should show proper error message for invalid entry name' () { - when: - // Use dsl_eval with an invalid entry name to trigger the error - dsl_eval('invalidEntry', ''' - workflow validWorkflow { - /println 'valid'/ - } - - workflow { - /println 'default'/ - } - ''') - - then: - def err = thrown(IllegalArgumentException) - err.message.contains('Unknown workflow entry name: invalidEntry') - } - } diff --git a/modules/nextflow/src/test/groovy/nextflow/script/ScriptIncludesTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/ScriptIncludesTest.groovy index 81a53a492f..1e5c728fef 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/ScriptIncludesTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/ScriptIncludesTest.groovy @@ -18,13 +18,12 @@ package nextflow.script import java.nio.file.Files -import nextflow.NextflowMeta import nextflow.exception.MissingProcessException import nextflow.exception.ScriptCompilationException import spock.lang.Timeout import test.Dsl2Spec -import test.MockScriptRunner -import test.TestHelper + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso @@ -32,48 +31,6 @@ import test.TestHelper @Timeout(10) class ScriptIncludesTest extends Dsl2Spec { - def 'should catch wrong script' () { - given: - def test = Files.createTempDirectory('test') - def lib = Files.createDirectory(test.toAbsolutePath()+"/lib") - def MODULE = lib.resolve('Foo.groovy') - def SCRIPT = test.resolve('main.nf') - - MODULE.text = ''' - class Foo { - String id - } - ''' - - SCRIPT.text = """ - include { Foo } from "$MODULE" - - process foo { - input: - val value - - output: - path '*.txt' - - script: - "echo 'hello'" - } - workflow { - foo(Channel.of(new Foo(id: "hello_world"))) - } - """ - - when: - new MockScriptRunner().setScript(SCRIPT).execute() - - then: - def err = thrown(ScriptCompilationException) - err.message == """\ - Module compilation error - - file : $MODULE - """.stripIndent().rightTrim() - } - def 'should invoke foreign functions' () { given: def folder = Files.createTempDirectory('test') @@ -83,41 +40,42 @@ class ScriptIncludesTest extends Dsl2Spec { MODULE.text = ''' def alpha() { return 'this is alpha result' - } - - def bravo(x) { + } + + def bravo(x) { return x.reverse() } - + def gamma(x,y) { return "$x and $y" } ''' - SCRIPT.text = """ - include { alpha; bravo; gamma } from "$MODULE" - + SCRIPT.text = """ + include { alpha; bravo; gamma } from "$MODULE" + def local_func() { return "I'm local" } - - ret1 = alpha() - ret2 = bravo('Hello') - ret3 = gamma('Hola', 'mundo') - ret4 = local_func() + + workflow { + [ + alpha(), + bravo('Hello'), + gamma('Hola', 'mundo'), + local_func() + ] + } """ when: - def runner = new MockScriptRunner() - def binding = runner.session.binding - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: - binding.ret1 == 'this is alpha result' - binding.ret2 == 'olleH' - binding.ret3 == 'Hola and mundo' - binding.ret4 == "I'm local" - binding.ret4 == result + result[0] == 'this is alpha result' + result[1] == 'olleH' + result[2] == 'Hola and mundo' + result[3] == "I'm local" } def 'should invoke foreign functions from operator' () { @@ -129,29 +87,25 @@ class ScriptIncludesTest extends Dsl2Spec { MODULE.text = ''' def foo(str) { return str.reverse() - } + } ''' - SCRIPT.text = """ - include { foo } from "$MODULE" + SCRIPT.text = """ + include { foo } from "$MODULE" workflow { - emit: - channel.of('hello world').map { foo(it) } + channel.value('hello world').map { foo(it) } } """ when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 'dlrow olleh' } - def 'should allow duplicate functions' () { + def 'should allow functions with default args' () { given: - NextflowMeta.instance.strictMode(true) - and: def folder = Files.createTempDirectory('test') def MODULE = folder.resolve('module.nf') def SCRIPT = folder.resolve('main.nf') @@ -162,32 +116,23 @@ class ScriptIncludesTest extends Dsl2Spec { } ''' - SCRIPT.text = """ - include { foo } from "$MODULE" + SCRIPT.text = """ + include { foo } from "$MODULE" workflow { - emit: - channel.of('hello world').map { - [ witharg : foo(it), withdefault : foo() ] - } + [ witharg: foo('hello world'), withdefault: foo() ] } """ when: - def result = new MockScriptRunner() .setScript(SCRIPT).execute() - def map = result.val + def result = runScript(SCRIPT) then: - map - map.witharg == 'hello world'.reverse() - map.withdefault == 'foo'.reverse() - - cleanup: - NextflowMeta.instance.strictMode(false) + result instanceof Map + result.witharg == 'hello world'.reverse() + result.withdefault == 'foo'.reverse() } def 'should allow multiple signatures of function' () { given: - NextflowMeta.instance.strictMode(true) - and: def folder = Files.createTempDirectory('test') def MODULE = folder.resolve('module.nf') def SCRIPT = folder.resolve('main.nf') @@ -198,33 +143,27 @@ class ScriptIncludesTest extends Dsl2Spec { } def foo(c1, c2){ return c1+"-"+c2 - } + } ''' - SCRIPT.text = """ - include { foo } from "$MODULE" + SCRIPT.text = """ + include { foo } from "$MODULE" workflow { - emit: - channel.fromList( foo() ).flatMap { foo(it, it*2) } + foo().collect { foo(it, it*2) } } """ when: - def result = new MockScriptRunner() .setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: - result.val == '1-2' - result.val == '2-4' - result.val == '3-6' - - cleanup: - NextflowMeta.instance.strictMode(false) + result[0] == '1-2' + result[1] == '2-4' + result[2] == '3-6' } def 'should fail if no signatures of function founded' () { given: - NextflowMeta.instance.strictMode(true) - and: def folder = Files.createTempDirectory('test') def MODULE = folder.resolve('module.nf') def SCRIPT = folder.resolve('main.nf') @@ -235,25 +174,21 @@ class ScriptIncludesTest extends Dsl2Spec { } def foo(c1, c2){ return c1+"-"+c2 - } + } ''' - SCRIPT.text = """ - include { foo } from "$MODULE" + SCRIPT.text = """ + include { foo } from "$MODULE" workflow { - emit: - channel.of( foo(1, 2, 3) ) + channel.of( foo(1, 2, 3) ) } """ when: - def result = new MockScriptRunner() .setScript(SCRIPT).execute() + runScript(SCRIPT) then: thrown(MissingProcessException) - - cleanup: - NextflowMeta.instance.strictMode(false) } def 'should invoke a workflow from include' () { @@ -264,45 +199,40 @@ class ScriptIncludesTest extends Dsl2Spec { MODULE.text = ''' process foo { - input: val data + input: val data output: val result exec: result = "$data mundo" - } - + } + process bar { - input: val data + input: val data output: val result - exec: + exec: result = data.toUpperCase() - } - + } + workflow alpha { take: data main: foo(data) bar(foo.output) emit: bar.out } - ''' SCRIPT.text = """ - include { alpha } from "$MODULE" - + include { alpha } from "$MODULE" + workflow { - main: alpha('Hello') - emit: alpha.out + alpha('Hello') } """ when: - def runner = new MockScriptRunner() - def binding = runner.session.binding - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 'HELLO MUNDO' - binding.variables.alpha == null } @@ -314,19 +244,18 @@ class ScriptIncludesTest extends Dsl2Spec { MODULE.text = ''' process foo { - input: val data + input: val data output: val result exec: result = "$data mundo" - } - + } + process bar { - input: val data + input: val data output: val result - exec: + exec: result = data.toUpperCase() - } - + } ''' SCRIPT.text = """ @@ -336,29 +265,23 @@ class ScriptIncludesTest extends Dsl2Spec { take: data main: foo(data) bar(foo.output) - emit: bar.out + emit: bar.out } - + workflow { - main: alpha('Hello') - emit: alpha.out + alpha('Hello') } """ when: - def runner = new MockScriptRunner() - def binding = runner.session.binding - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 'HELLO MUNDO' - !binding.hasVariable('alpha') - !binding.hasVariable('foo') - !binding.hasVariable('bar') } - def 'should invoke an anonymous workflow' () { + def 'should invoke an entry workflow' () { given: def folder = Files.createTempDirectory('test') def MODULE = folder.resolve('module.nf') @@ -366,35 +289,31 @@ class ScriptIncludesTest extends Dsl2Spec { MODULE.text = ''' process foo { - input: val data + input: val data output: val result exec: result = "$data mundo" - } - + } + process bar { - input: val data + input: val data output: val result - exec: + exec: result = data.toUpperCase() - } - + } ''' SCRIPT.text = """ include { foo; bar } from "$MODULE" - - data = 'Hello' + workflow { - main: foo(data) - bar(foo.output) - emit: bar.out + data = 'Hello' + bar(foo(data)) } """ when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 'HELLO MUNDO' @@ -408,42 +327,36 @@ class ScriptIncludesTest extends Dsl2Spec { MODULE.text = ''' process foo { - input: val data + input: val data output: val result exec: result = "$data mundo" - } - + } + process bar { - input: val data + input: val data output: val result - exec: + exec: result = data.toUpperCase() - } - + } ''' SCRIPT.text = """ - include { foo; bar } from "$MODULE" - + include { foo; bar } from "$MODULE" + workflow { data = 'Hello' foo(data) bar(foo.output) - emit: bar.out + bar.out } """ when: - def runner = new MockScriptRunner() - def vars = runner.session.binding.variables - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 'HELLO MUNDO' - !vars.containsKey('data') - !vars.containsKey('foo') - !vars.containsKey('bar') } def 'should define a process and invoke it' () { @@ -455,25 +368,22 @@ class ScriptIncludesTest extends Dsl2Spec { MODULE.text = ''' process foo { input: val sample - output: stdout + output: stdout script: - /echo Hello $sample/ - } + "echo Hello $sample" + } ''' SCRIPT.text = """ - include { foo } from "$MODULE" - hello_ch = Channel.of('world') - + include { foo } from "$MODULE" + workflow { - main: foo(hello_ch) - emit: foo.out + foo('world') } """ when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: noExceptionThrown() result.val == 'echo Hello world' @@ -484,19 +394,19 @@ class ScriptIncludesTest extends Dsl2Spec { def 'should define a process with multiple inputs' () { given: - def folder = TestHelper.createInMemTempDir() + def folder = Files.createTempDirectory('test') def MODULE = folder.resolve('module.nf') def SCRIPT = folder.resolve('main.nf') MODULE.text = ''' process foo { - input: + input: val sample tuple val(pairId), val(reads) - output: - stdout + output: + stdout script: - /echo sample=$sample pairId=$pairId reads=$reads/ + "echo sample=$sample pairId=$pairId reads=$reads" } ''' @@ -504,33 +414,34 @@ class ScriptIncludesTest extends Dsl2Spec { include { foo } from './module.nf' workflow { - main: ch1 = Channel.of('world') - ch2 = Channel.value(['x', '/some/file']) - foo(ch1, ch2) - emit: foo.out + ch1 = channel.of('world') + ch2 = channel.value(['x', '/some/file']) + foo(ch1, ch2) } """ when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: noExceptionThrown() result.val == 'echo sample=world pairId=x reads=/some/file' + + cleanup: + folder?.deleteDir() } def 'should compose processes' () { given: - def folder = TestHelper.createInMemTempDir() + def folder = Files.createTempDirectory('test') def MODULE = folder.resolve('module.nf') def SCRIPT = folder.resolve('main.nf') MODULE.text = ''' process foo { - input: + input: val alpha - output: + output: val delta val gamma script: @@ -538,46 +449,49 @@ class ScriptIncludesTest extends Dsl2Spec { gamma = 'world' /nope/ } - + process bar { input: val xx - val yy + val yy output: stdout script: - /echo $xx $yy/ + "echo $xx $yy" } ''' and: - SCRIPT.text = """ - include { foo; bar } from './module.nf' + SCRIPT.text = """ + include { foo; bar } from './module.nf' workflow { - main: bar( foo('Ciao') ) - emit: bar.out + (delta, gamma) = foo('Ciao') + bar( delta, gamma ) } """ when: - def result = dsl_eval(SCRIPT) + def result = runScript(SCRIPT) then: result.val == 'echo Ciao world' + + cleanup: + folder?.deleteDir() } def 'should use multiple assignment' () { given: - def folder = TestHelper.createInMemTempDir() + def folder = Files.createTempDirectory('test') def MODULE = folder.resolve('module.nf') def SCRIPT = folder.resolve('main.nf') MODULE.text = ''' process foo { - input: + input: val alpha - output: + output: val delta val gamma script: @@ -585,77 +499,42 @@ class ScriptIncludesTest extends Dsl2Spec { gamma = 'world' /nope/ } - + process bar { input: val xx - val yy + val yy output: stdout script: - /echo $xx $yy/ + "echo $xx $yy" } ''' and: - SCRIPT.text = """ - include { foo } from './module.nf' - + SCRIPT.text = """ + include { foo } from './module.nf' + workflow { - main: (ch0, ch1) = foo('Ciao') - emit: ch0; ch1 + (ch0, ch1) = foo('Ciao') + [ ch0, ch1 ] } """ - + when: - def result = dsl_eval(SCRIPT) + def result = runScript(SCRIPT) then: result[0].val == 'Ciao' result[1].val == 'world' - } - - - def 'should inject params in module' () { - given: - def folder = TestHelper.createInMemTempDir() - def MODULE = folder.resolve('module.nf') - def SCRIPT = folder.resolve('main.nf') - - MODULE.text = ''' - params.foo = 'x' - params.bar = 'y' - - process foo { - output: stdout - script: - /echo $params.foo $params.bar/ - } - ''' - - // inject params in the module - // and invoke the process 'foo' - SCRIPT.text = """ - include { foo } from "./module.nf" params(foo:'Hello', bar: 'world') - - workflow { - main: foo() - emit: foo.out - } - """ - when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() - then: - noExceptionThrown() - result.val == 'echo Hello world' - + cleanup: + folder?.deleteDir() } def 'should invoke custom functions' () { given: - def folder = TestHelper.createInMemTempDir() + def folder = Files.createTempDirectory('test') def MODULE = folder.resolve('module.nf') def SCRIPT = folder.resolve('main.nf') @@ -663,174 +542,162 @@ class ScriptIncludesTest extends Dsl2Spec { def foo(str) { str.reverse() } - + def bar(a, b) { return "$a $b!" } ''' - SCRIPT.text = """ + SCRIPT.text = """ include { foo; bar } from './module.nf' - def str = foo('dlrow') - return bar('Hello', str) + workflow { + def str = foo('dlrow') + return bar('Hello', str) + } """ when: - def result = new MockScriptRunner() - .setScript(SCRIPT) - .execute() + def result = runScript(SCRIPT) then: noExceptionThrown() result == 'Hello world!' - } - - def 'should access module variables' () { - given: - def folder = TestHelper.createInMemTempDir() - def MODULE = folder.resolve('module.nf') - def SCRIPT = folder.resolve('main.nf') - - MODULE.text = ''' - params.x = 'Hello world' - FOO = params.x - - process foo { - output: stdout - script: - "echo $FOO" - } - ''' - - SCRIPT.text = """ - include { foo } from './module.nf' params(x: 'Hola mundo') - - workflow { - main: foo() - emit: foo.out - } - """ - when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() - then: - noExceptionThrown() - result.val == 'echo Hola mundo' + cleanup: + folder?.deleteDir() } def 'should not fail when invoking a process in a module' () { given: - def folder = TestHelper.createInMemTempDir() + def folder = Files.createTempDirectory('test') def MODULE = folder.resolve('module.nf') def SCRIPT = folder.resolve('main.nf') - MODULE.text = ''' + MODULE.text = ''' process foo { - /hello/ - } - - workflow { foo() } + script: + /hello/ + } + + workflow { foo() } ''' - SCRIPT.text = """ + SCRIPT.text = """ include { foo } from './module.nf' - println 'hello' + + workflow { + println 'hello' + } """ when: - def runner = new MockScriptRunner() - runner.setScript(SCRIPT).execute() + runScript(SCRIPT) then: noExceptionThrown() + + cleanup: + folder?.deleteDir() } def 'should include modules' () { given: - def folder = TestHelper.createInMemTempDir(); + def folder = Files.createTempDirectory('test') folder.resolve('org').mkdir() def MODULE = folder.resolve('org/bio.nf') def SCRIPT = folder.resolve('main.nf') - MODULE.text = ''' + MODULE.text = ''' process foo { - /hello/ - } + script: + /hello/ + } ''' - SCRIPT.text = """ - include { foo } from './org/bio' - + SCRIPT.text = """ + include { foo } from './org/bio' + workflow { foo() } """ when: - def runner = new MockScriptRunner() - runner.setScript(SCRIPT).execute() + runScript(SCRIPT) then: noExceptionThrown() + + cleanup: + folder?.deleteDir() } def 'should include only named component' () { given: def folder = Files.createTempDirectory('test') def MODULE = folder.resolve('module.nf') + def SCRIPT = folder.resolve('main.nf') MODULE.text = ''' def alpha() { return 'this is alpha result' - } - - def bravo(x) { - return x.reverse() } + def bravo(x) { + return x.reverse() + } ''' when: - def SCRIPT = """ + SCRIPT.text = """ include { alpha } from "$MODULE" - alpha() + + workflow { + alpha() + } """ - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result == 'this is alpha result' when: - SCRIPT = """ + SCRIPT.text = """ include { alpha as FOO } from "$MODULE" - FOO() + + workflow { + FOO() + } """ - runner = new MockScriptRunner() - result = runner.setScript(SCRIPT).execute() + result = runScript(SCRIPT) then: result == 'this is alpha result' when: - SCRIPT = """ + SCRIPT.text = """ include { alpha as FOO } from "$MODULE" - alpha() + + workflow { + alpha() + } """ - runner = new MockScriptRunner() - runner.setScript(SCRIPT).execute() + result = runScript(SCRIPT) then: - thrown(MissingMethodException) + thrown(ScriptCompilationException) when: - SCRIPT = """ + SCRIPT.text = """ include { alpha } from "$MODULE" - bravo() + + workflow { + bravo() + } """ - runner = new MockScriptRunner() - runner.setScript(SCRIPT).execute() + result = runScript(SCRIPT) then: - thrown(MissingMethodException) + thrown(ScriptCompilationException) + cleanup: + folder?.deleteDir() } @@ -842,32 +709,30 @@ class ScriptIncludesTest extends Dsl2Spec { MODULE.text = ''' process foo { - input: val data + input: val data output: val result exec: result = data.toUpperCase() - } + } ''' SCRIPT.text = """ - include { foo } from "$MODULE" - include { foo as bar } from "$MODULE" + include { foo } from "$MODULE" + include { foo as bar } from "$MODULE" workflow { - foo('Hello') - bar('World') - emit: foo.out - emit: bar.out + [ foo('Hello'), bar('World') ] } """ when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() - + def result = runScript(SCRIPT) then: result[0].val == 'HELLO' result[1].val == 'WORLD' + + cleanup: + folder?.deleteDir() } @@ -875,53 +740,57 @@ class ScriptIncludesTest extends Dsl2Spec { given: def folder = Files.createTempDirectory('test') def MODULE = folder.resolve('module.nf') + def SCRIPT = folder.resolve('main.nf') MODULE.text = ''' process producer { output: stdout - shell: "echo Hello" + script: "echo Hello" } - + process consumer { input: file "foo" output: stdout - shell: + script: "cmd consumer 1" } - + process another_consumer { input: file "foo" output: stdout - shell: "cmd consumer 2" + script: "cmd consumer 2" } - + workflow flow1 { emit: producer | consumer | map { it.toUpperCase() } } - + workflow flow2 { emit: producer | another_consumer | map { it.toUpperCase() } } '''.stripIndent() - when: - def result = dsl_eval(""" - include { flow1; flow2 } from "$MODULE" - - workflow { + SCRIPT.text = """ + include { flow1; flow2 } from "$MODULE" + + workflow { flow1() flow2() - emit: - flow1.out - flow2.out + + [ flow1.out, flow2.out ] } - """) + """ + + when: + def result = runScript(SCRIPT) then: result[0].val == 'CMD CONSUMER 1' result[1].val == 'CMD CONSUMER 2' + cleanup: + folder?.deleteDir() } @@ -934,138 +803,32 @@ class ScriptIncludesTest extends Dsl2Spec { MODULE.text = ''' process foo { - input: val data + input: val data output: val result exec: result = data.toUpperCase() - } + } ''' SCRIPT.text = """ - include { foo; foo as bar } from "$MODULE" + include { foo; foo as bar } from "$MODULE" workflow { foo('Hello') bar('World') - emit: foo.out - emit: bar.out + [ foo.out, bar.out ] } """ when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result[0].val == 'HELLO' result[1].val == 'WORLD' - } - - def 'should inherit module params' () { - given: - def folder = Files.createTempDirectory('test') - def MODULE = folder.resolve('module.nf') - def SCRIPT = folder.resolve('main.nf') - - MODULE.text = ''' - params.alpha = 'first' - params.omega = 'last' - - process foo { - output: val result - exec: - result = "$params.alpha $params.omega".toUpperCase() - } - ''' - - SCRIPT.text = """ - params.alpha = 'owner' - include { foo } from "$MODULE" - - workflow { - foo() - emit: foo.out - } - """ - - when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() - then: - result.val == 'OWNER LAST' - } - - def 'should override module params' () { - given: - def folder = Files.createTempDirectory('test') - def MODULE = folder.resolve('module.nf') - def SCRIPT = folder.resolve('main.nf') - - MODULE.text = ''' - params.alpha = 'first' - params.omega = 'last' - - process foo { - output: val result - exec: - result = "$params.alpha $params.omega".toUpperCase() - } - ''' - - SCRIPT.text = """ - params.alpha = 'owner' - include { foo } from "$MODULE" params(alpha:'aaa', omega:'zzz') - - workflow { - foo() - emit: foo.out - } - """ - - when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() - - then: - result.val == 'AAA ZZZ' - } - - def 'should extends module params' () { - given: - def folder = Files.createTempDirectory('test') - def MODULE = folder.resolve('module.nf') - def SCRIPT = folder.resolve('main.nf') - - MODULE.text = ''' - params.alpha = 'first' - params.omega = 'last' - - process foo { - output: val result - exec: - result = "$params.alpha $params.omega".toUpperCase() - } - ''' - - SCRIPT.text = """ - params.alpha = 'one' - params.omega = 'two' - - include { foo } from "$MODULE" addParams(omega:'zzz') - - workflow { - foo() - emit: foo.out - } - """ - - when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() - - then: - result.val == 'ONE ZZZ' + cleanup: + folder?.deleteDir() } def 'should declare moduleDir path' () { @@ -1076,74 +839,41 @@ class ScriptIncludesTest extends Dsl2Spec { MODULE.text = """ def foo () { return true } - assert moduleDir == file("$folder/module/dir") - assert projectDir == file("$folder") - assert launchDir == file('.') - """ - SCRIPT.text = """ - include { foo } from "$MODULE" - - assert moduleDir == file("$folder") - assert projectDir == file("$folder") - assert launchDir == file('.') - - workflow { true } - """ - - when: - new MockScriptRunner() - .setScript(SCRIPT) - .execute() - then: - true - } - - def 'should not allow unwrapped include' () { - given: - def folder = TestHelper.createInMemTempDir() - def MODULE = folder.resolve('module.nf') - def SCRIPT = folder.resolve('main.nf') - - MODULE.text = ''' - params.foo = 'x' - params.bar = 'y' - - process foo { - output: stdout - script: - /echo $params.foo $params.bar/ + workflow { + assert moduleDir == file("$folder/module/dir") + assert projectDir == file("$folder") + assert launchDir == file('.') } - ''' + """ - // inject params in the module - // and invoke the process 'foo' SCRIPT.text = """ - include foo from "./module.nf" params(foo:'Hello', bar: 'world') - - workflow { - main: foo() - emit: foo.out + include { foo } from "$MODULE" + + workflow { + assert moduleDir == file("$folder") + assert projectDir == file("$folder") + assert launchDir == file('.') } """ when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + runScript(SCRIPT) then: - def e = thrown(DeprecationException) - e.message == "Unwrapped module inclusion is deprecated -- Replace `include foo from './MODULE/PATH'` with `include { foo } from './MODULE/PATH'`" + noExceptionThrown() + cleanup: + folder?.deleteDir() } def 'should not allow include nested within a workflow' () { given: - def folder = TestHelper.createInMemTempDir() + def folder = Files.createTempDirectory('test') def MODULE = folder.resolve('module.nf') def SCRIPT = folder.resolve('main.nf') MODULE.text = ''' - + process foo { script: /echo hello/ @@ -1151,43 +881,49 @@ class ScriptIncludesTest extends Dsl2Spec { ''' SCRIPT.text = """ - workflow { + workflow { include { foo } from "./module.nf" foo() } """ when: - new MockScriptRunner().setScript(SCRIPT).execute() + runScript(SCRIPT) then: - def e = thrown(IllegalStateException) - e.message == "Include statement is not allowed within a workflow definition" + thrown(ScriptCompilationException) + cleanup: + folder?.deleteDir() } def 'should should allow invoking function passing gstring' () { given: def folder = Files.createTempDirectory('test') def MODULE = folder.resolve('module.nf') + def SCRIPT = folder.resolve('main.nf') MODULE.text = ''' def alpha(String str) { return str.reverse() - } + } ''' when: - def SCRIPT = """ + SCRIPT.text = """ include { alpha } from "$MODULE" - - def x = "world" - def y = "Hello \$x" - - return alpha(y) + + workflow { + def x = "world" + def y = "Hello \$x" + + alpha(y) + } """ - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result == 'dlrow olleH' + + cleanup: + folder?.deleteDir() } } diff --git a/modules/nextflow/src/test/groovy/nextflow/script/ScriptMetaTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/ScriptMetaTest.groovy index d85fa16218..7c6f55c26d 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/ScriptMetaTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/ScriptMetaTest.groovy @@ -20,7 +20,6 @@ import java.nio.file.Files import groovy.transform.InheritConstructors import nextflow.NF -import nextflow.exception.DuplicateModuleFunctionException import test.Dsl2Spec import test.TestHelper @@ -164,19 +163,6 @@ class ScriptMetaTest extends Dsl2Spec { } - def 'should not throw a duplicate process name exception' () { - given: - def script = new FooScript(new ScriptBinding()) - def meta = new ScriptMeta(script) - def comp1 = Mock(ComponentDef) - - when: - meta.@imports.clear() - meta.addModule0(comp1) - then: - 2 * comp1.getName() >> 'foo' - } - def 'should get module bundle' () { given: def folder = TestHelper.createInMemTempDir() @@ -202,46 +188,4 @@ class ScriptMetaTest extends Dsl2Spec { bundle.getEntries() == ['foo.txt', 'bar.txt'] as Set } - - def 'should throw duplicate name exception' () { - - given: - def script1 = new FooScript(new ScriptBinding()) - def script2 = new FooScript(new ScriptBinding()) - def meta1 = new ScriptMeta(script1) - def meta2 = new ScriptMeta(script2) - - // import module into main script - def func2 = new FunctionDef(name: 'func1', alias: 'func1') - def proc2 = createProcessDef(script2, 'proc1') - def work2 = new WorkflowDef(name: 'work1') - meta2.addDefinition(proc2, func2, work2) - - meta1.addModule(meta2, 'func1', null) - meta1.addModule(meta2, 'proc1', null) - meta1.addModule(meta2, 'work1', null) - - // attempt to define duplicate components in main script - def func1 = new FunctionDef(name: 'func1', alias: 'func1') - def proc1 = createProcessDef(script1, 'proc1') - def work1 = new WorkflowDef(name: 'work1') - - when: - meta1.addDefinition(func1) - then: - def e = thrown(DuplicateModuleFunctionException) - e.message.contains "A function named 'func1' is already defined" - - when: - meta1.addDefinition(proc1) - then: - e = thrown(DuplicateModuleFunctionException) - e.message.contains "A process named 'proc1' is already defined" - - when: - meta1.addDefinition(work1) - then: - e = thrown(DuplicateModuleFunctionException) - e.message.contains "A workflow named 'work1' is already defined" - } } diff --git a/modules/nextflow/src/test/groovy/nextflow/script/ScriptPipesTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/ScriptPipesTest.groovy index 20c44c2525..f8d6164e4c 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/ScriptPipesTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/ScriptPipesTest.groovy @@ -1,10 +1,9 @@ package nextflow.script -import java.nio.file.Files - import spock.lang.Timeout import test.Dsl2Spec -import test.MockScriptRunner + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso @@ -30,17 +29,13 @@ class ScriptPipesTest extends Dsl2Spec { } workflow { - main: Channel.of('Hello') | map { it.reverse() } | (foo & bar) - emit: - foo.out - bar.out + channel.of('Hello') | map { it.reverse() } | (foo & bar) } ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result[0].val == 'olleH mundo' @@ -66,13 +61,12 @@ class ScriptPipesTest extends Dsl2Spec { } workflow { - emit: Channel.of('Hola') | foo | map { it.reverse() } | bar + channel.of('Hola') | foo | map { it.reverse() } | bar } ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 'DLROW ALOH' @@ -105,13 +99,12 @@ class ScriptPipesTest extends Dsl2Spec { // the multiple output channels // to the `bar` process receiving multiple inputs workflow { - emit: Channel.of('hello') | foo | bar + channel.of('hello') | foo | bar } ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 'olleh + HELLO' @@ -136,13 +129,12 @@ class ScriptPipesTest extends Dsl2Spec { // pipe the multiple output channels // to the `concat` operator workflow { - emit: Channel.of('hola') | foo | concat + channel.of('hola') | foo | concat } ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 'aloh' @@ -162,13 +154,12 @@ class ScriptPipesTest extends Dsl2Spec { } workflow { - emit: foo | map { it.reverse() } + foo | map { it.reverse() } } ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 'aloh' @@ -192,13 +183,12 @@ class ScriptPipesTest extends Dsl2Spec { } workflow { - emit: foo | bar + foo | bar } ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 'HOLA' @@ -216,13 +206,12 @@ class ScriptPipesTest extends Dsl2Spec { } workflow { - emit: Channel.of(1,2,3) | square | collect + channel.of(1,2,3) | square | collect } ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val.sort() == [1, 4, 9] @@ -232,11 +221,11 @@ class ScriptPipesTest extends Dsl2Spec { def 'should pipe branch output to concat operator' () { given: def SCRIPT =''' - Channel.of(10,20,30) | branch { foo: it <=10; bar: true } | concat + channel.of(10,20,30) | branch { foo: it <=10; bar: true } | concat ''' when: - def result = new MockScriptRunner().setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 10 result.val == 20 @@ -248,193 +237,20 @@ class ScriptPipesTest extends Dsl2Spec { given: def SCRIPT =''' process foo { - input: val x - input: val y + input: val x ; val y output: val ret exec: ret=x*2+y } workflow { - emit: Channel.of(10,20) | branch { foo: it <=10; bar: true } | foo + channel.of(10,20) | branch { foo: it <=10; bar: true } | foo } ''' when: - def result = new MockScriptRunner().setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 40 } - def 'should compose custom funs' () { - given: - def SCRIPT = """ - process foo { - output: val ret - exec: ret=10 - } - - def bar(ch) { - ch.map { it +1 } - } - - workflow { - emit: foo | bar | map{ it*2 } - } - """ - - when: - def result = new MockScriptRunner().setScript(SCRIPT).execute() - then: - result.val == 22 - - } - - def 'should compose custom funs/2' () { - given: - def SCRIPT = """ - process foo { - output: - val x - val y - exec: - x=1; y=2 - } - - def bar(ch1, ch2) { - ch1.combine(ch2) - } - - workflow { - emit: foo | bar | view - } - """ - - when: - def result = new MockScriptRunner().setScript(SCRIPT).execute() - then: - result.val == [1,2] - - } - - def 'should compose custom funs/3' () { - given: - def SCRIPT = """ - process foo { - input: - val str - output: - val x - exec: - x=str.reverse() - } - - def init(str='hi'){ - Channel.of(str) - } - - workflow { - emit: init | foo | view - } - """ - - when: - def result = new MockScriptRunner().setScript(SCRIPT).execute() - then: - result.val == 'hi'.reverse() - - } - - def 'should compose custom funs/4' () { - given: - def SCRIPT = """ - process foo { - input: - val str - output: - val x - exec: - x=str.reverse() - } - - def init(str='hi'){ - Channel.of(str) - } - - workflow { - emit: init('hello') | foo | view - } - """ - - when: - def result = new MockScriptRunner().setScript(SCRIPT).execute() - then: - result.val == 'hello'.reverse() - - } - - def 'should compose custom funs/5' () { - given: - def SCRIPT = """ - process foo { - input: - val str - output: - val x - exec: - x=str.reverse() - } - - def init(str='hi'){ - Channel.of(str) - } - - def bar(ch1=null) { - ch1.map{ it.toUpperCase() } - } - - workflow { - emit: init | foo | bar | view - } - """ - - when: - def result = new MockScriptRunner().setScript(SCRIPT).execute() - then: - result.val == 'HI'.reverse() - - } - - def 'should compose imported funs' () { - given: - def folder = Files.createTempDirectory('test') - def MODULE = folder.resolve('module.nf') - MODULE.text = ''' - process foo { - output: val ret - exec: ret=10 - } - - def bar(ch) { - ch.map { it +1 } - } - - ''' - def SCRIPT = """ - include{ foo; bar } from "$MODULE" - - workflow { - emit: foo | bar | map{ it*3 } - } - """ - - when: - def result = new MockScriptRunner().setScript(SCRIPT).execute() - then: - result.val == 33 - - - cleanup: - folder?.deleteDir() - } - } diff --git a/modules/nextflow/src/test/groovy/nextflow/script/ScriptProcessRunTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/ScriptProcessRunTest.groovy index 856b096e98..f69616d3c6 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/ScriptProcessRunTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/ScriptProcessRunTest.groovy @@ -3,7 +3,8 @@ package nextflow.script import java.nio.file.Files import test.Dsl2Spec -import test.MockScriptRunner + +import static test.ScriptHelper.* /** * Tests for single process execution feature that allows running processes @@ -27,8 +28,7 @@ class ScriptProcessRunTest extends Dsl2Spec { ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: // For single process execution, the result should contain the process output @@ -52,8 +52,7 @@ class ScriptProcessRunTest extends Dsl2Spec { """ when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result != null @@ -64,31 +63,6 @@ class ScriptProcessRunTest extends Dsl2Spec { Files.deleteIfExists(tempFile) } - def 'should execute single process with tuple input' () { - given: - def SCRIPT = ''' - params.'meta.id' = 'SAMPLE_001' - params.'meta.name' = 'test' - params.threads = 4 - - process testProcess { - input: tuple val(meta), val(threads) - output: val result - exec: - result = "Sample: ${meta.id}, Threads: $threads" - } - ''' - - when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() - - then: - result != null - println "Tuple result: $result" - println "Tuple result class: ${result?.getClass()}" - } - def 'should handle multiple processes by running the first one' () { given: def SCRIPT = ''' @@ -110,8 +84,7 @@ class ScriptProcessRunTest extends Dsl2Spec { ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result != null @@ -131,37 +104,10 @@ class ScriptProcessRunTest extends Dsl2Spec { ''' when: - def runner = new MockScriptRunner() - runner.setScript(SCRIPT).execute() + runScript(SCRIPT) then: def e = thrown(Exception) e.message.contains('Missing required parameter: --requiredParam') } - - def 'should handle complex parameter mapping' () { - given: - def SCRIPT = ''' - params.'sample.id' = 'S001' - params.'sample.name' = 'TestSample' - params.'config.threads' = 8 - - process complexProcess { - input: val sample - input: val config - output: val result - exec: - result = "Sample: ${sample.id}, Config: ${config.threads}" - } - ''' - - when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() - - then: - result != null - println "Complex result: $result" - println "Complex result class: ${result?.getClass()}" - } } diff --git a/modules/nextflow/src/test/groovy/nextflow/script/ScriptRecurseTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/ScriptRecurseTest.groovy index 2723b2a051..95bf20c86e 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/ScriptRecurseTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/ScriptRecurseTest.groovy @@ -21,7 +21,8 @@ import nextflow.Channel import nextflow.NextflowMeta import spock.lang.Timeout import test.Dsl2Spec -import test.MockScriptRunner + +import static test.ScriptHelper.* /** * @@ -44,14 +45,13 @@ class ScriptRecurseTest extends Dsl2Spec { } workflow { - main: foo.recurse(1).times(3) - emit: foo.out + foo.recurse(1).times(3) + foo.out } ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 2 result.val == 3 @@ -70,14 +70,13 @@ class ScriptRecurseTest extends Dsl2Spec { } workflow { - main: foo.recurse(1).until { it >= 4 } - emit: foo.out + foo.recurse(1).until { it >= 4 } + foo.out } ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 2 result.val == 3 @@ -112,14 +111,13 @@ class ScriptRecurseTest extends Dsl2Spec { } workflow { - main: group.recurse(1).times(3) - emit: group.out + group.recurse(1).times(3) + group.out } ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 4 result.val == 25 @@ -140,16 +138,13 @@ class ScriptRecurseTest extends Dsl2Spec { } workflow { - main: data = channel.of(10,20,30) foo.scan(data) - emit: foo.out } ''' when: - def runner = new MockScriptRunner() - def result = runner.setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 11 // 10 +1 result.val == 32 // 20 + 11 +1 diff --git a/modules/nextflow/src/test/groovy/nextflow/script/ScriptRunnerTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/ScriptRunnerTest.groovy index d2f6a2efdd..f124883b2f 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/ScriptRunnerTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/ScriptRunnerTest.groovy @@ -17,16 +17,15 @@ package nextflow.script import groovyx.gpars.dataflow.DataflowVariable -import nextflow.config.ConfigParserFactory -import nextflow.exception.AbortRunException -import nextflow.exception.ProcessUnrecoverableException +import nextflow.exception.ScriptCompilationException +import nextflow.extension.Bolts import nextflow.processor.TaskProcessor import nextflow.util.Duration import nextflow.util.MemoryUnit import spock.lang.Timeout import test.Dsl2Spec -import test.MockScriptRunner -import test.MockSession + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso @@ -51,19 +50,15 @@ class ScriptRunnerTest extends Dsl2Spec { script: "echo Hello world" } - + workflow { - main: sayHello() - emit: sayHello.out + sayHello() } """ when: - def result = new MockScriptRunner(config) - .setScript(script) - .execute() + def result = runScript(script, config) - // when no outputs are specified, the 'stdout' is the default output then: result instanceof DataflowVariable result.val == "echo Hello world" @@ -83,22 +78,20 @@ class ScriptRunnerTest extends Dsl2Spec { ''' process simpleTask { input: - val x + val x output: - stdout + stdout + script: """echo $x""" } workflow { - main: simpleTask(1) - emit: simpleTask.out + simpleTask(1) } ''' when: - new MockScriptRunner(config) - .setScript(script) - .execute() + runScript(script, config) def processor = TaskProcessor.currentProcessor() then: processor.name == 'simpleTask' @@ -117,17 +110,17 @@ class ScriptRunnerTest extends Dsl2Spec { output: stdout + script: """echo $x - $y""" } workflow { - main: simpleTask(1, channel.of(3)) - emit: simpleTask.out + simpleTask(1, channel.of(3)) } ''' when: - def result = new MockScriptRunner().setScript(script).execute() + def result = runScript(script) then: result.val == 'echo 1 - 3' @@ -142,25 +135,24 @@ class ScriptRunnerTest extends Dsl2Spec { ''' process simpleTask { input: - val x + val x output: stdout + script: "echo $x" } - + workflow { - main: simpleTask(1) - emit: simpleTask.out + simpleTask(1) } ''' when: - def runner = new MockScriptRunner().setScript(script) - runner.execute() + def result = runScript(script) then: - runner.result.val == 'echo 1' + result.val == 'echo 1' TaskProcessor.currentProcessor().name == 'simpleTask' } @@ -170,26 +162,24 @@ class ScriptRunnerTest extends Dsl2Spec { given: def script = ''' - X = 1 - Y = 200 process simpleTask { input: + val X val Y output: stdout + script: "$X-$Y-3" } workflow { - main: simpleTask(2) - emit: simpleTask.out + simpleTask(1, 2) } ''' when: - def runner = new MockScriptRunner().setScript(script) - def result = runner.execute() + def result = runScript(script) then: result.val == '1-2-3' @@ -199,27 +189,25 @@ class ScriptRunnerTest extends Dsl2Spec { given: def script = ''' - X = 1 - Y = 200 process simpleTask { input: + val X val Y output: stdout + script: def Z = 3 "$X-$Y-$Z" } workflow { - main: simpleTask(2) - emit: simpleTask.out + simpleTask(1, 2) } ''' when: - def runner = new MockScriptRunner().setScript(script) - def result = runner.execute() + def result = runScript(script) then: result.val == '1-2-3' @@ -239,40 +227,12 @@ class ScriptRunnerTest extends Dsl2Spec { when: def config = [process:[executor: 'nope']] - def runner = new MockScriptRunner(config) - runner.setScript(script) .execute() + runScript(script, config) then: - thrown(AbortRunException) + def e = thrown(ScriptCompilationException) and: - runner.session.fault.error instanceof ProcessUnrecoverableException - runner.session.fault.error.cause instanceof MissingPropertyException - runner.session.fault.error.cause.message =~ /Unknown variable 'HELLO' -- .*/ - // if this fails, likely there's something wrong in the LoggerHelper#getErrorLine method - runner.session.fault.report =~ /No such variable: HELLO -- .*/ - - } - - - def 'test process fallback variable' () { - given: - def script = ''' - process simpleTask { - output: val(x) - exec: - x = "$HELLO" - } - - workflow { - main: simpleTask() - emit: simpleTask.out - } - ''' - and: - def config = [process: [executor: 'nope'], env: [HELLO: 'Hello world!']] - - expect: - new MockScriptRunner(config).setScript(script).execute().val == 'Hello world!' + e.cause.message.contains '`HELLO` is not defined' } @@ -280,27 +240,28 @@ class ScriptRunnerTest extends Dsl2Spec { def 'test process output file' () { given: def script = ''' - X = file('filename') - process simpleTask { input: file X output: stdout + script: "cat $X" } - workflow { - main: simpleTask(X) - emit: simpleTask.out + workflow { + X = file('filename') + simpleTask(X) } ''' and: def config = [process: [executor: 'nope']] - expect: - new MockScriptRunner(config).setScript(script).execute().val == 'cat filename' + when: + def result = runScript(script, config) + then: + result.val == 'cat filename' } @@ -308,36 +269,33 @@ class ScriptRunnerTest extends Dsl2Spec { def 'test process name options' ( ) { given: - // -- this represent the configuration file - def config = ''' + def config = loadConfig(''' process { executor = 'nope' memory = '333' withName: hola { cpus = '222'; time = '555' } withName: ciao { cpus = '999' } } - ''' + ''') def script = ''' process hola { penv 1 cpus 2 + script: 'echo hola' } - + workflow { hola() } ''' - and: - def session = new MockSession(ConfigParserFactory.create().parse(config)) - when: - new MockScriptRunner(session).setScript(script).execute() + runScript(script, config) def process = TaskProcessor.currentProcessor() then: - TaskProcessor.currentProcessor().config instanceof ProcessConfig + process.config instanceof ProcessConfig process.config.penv == 1 process.config.cpus == '222' // !! this value is overridden by the one in the config file process.config.memory == '333' @@ -348,8 +306,7 @@ class ScriptRunnerTest extends Dsl2Spec { def 'test process name options 2'( ) { given: - // -- this represent the configuration file - def config = ''' + def config = loadConfig(''' process { executor = 'nope' memory = '333' @@ -363,24 +320,22 @@ class ScriptRunnerTest extends Dsl2Spec { cpus = '999' } } - ''' + ''') def script = ''' process hola { penv 1 cpus 2 + script: 'echo hola' } - + workflow { hola() } ''' - and: - def session = new MockSession(ConfigParserFactory.create().parse(config)) - when: - new MockScriptRunner(session).setScript(script).execute() + runScript(script, config) def process = TaskProcessor.currentProcessor() then: @@ -395,27 +350,25 @@ class ScriptRunnerTest extends Dsl2Spec { def 'test module config'() { given: - // -- this represent the configuration file - def config = ''' + def config = loadConfig(''' process.executor = 'nope' process.module = 'a/1' - ''' + ''') def script = ''' process hola { module 'b/2' module 'c/3' + script: 'echo 1' } - - workflow { hola() } + + workflow { hola() } ''' - and: - def session = new MockSession(ConfigParserFactory.create().parse(config)) when: - new MockScriptRunner(session).setScript(script).execute() + runScript(script, config) def process = TaskProcessor.currentProcessor() then: @@ -430,30 +383,28 @@ class ScriptRunnerTest extends Dsl2Spec { /* * the module defined in the config file 'b/2' has priority and overrides the 'a/1' and 'c/3' */ - def config = ''' + def config = loadConfig(''' process { executor = 'nope' module = 'a/1' withName: hola { module = 'b/2:z/9' } } - ''' + ''') def script = ''' process hola { module 'c/3' module 'd/4' + script: 'echo 1' } - + workflow { hola() } ''' - and: - def session = new MockSession(ConfigParserFactory.create().parse(config)) - when: - new MockScriptRunner(session).setScript(script).execute() + runScript(script, config) def process = TaskProcessor.currentProcessor() then: @@ -468,23 +419,21 @@ class ScriptRunnerTest extends Dsl2Spec { /* * the module defined in the config file 'b/2' has priority and overrides the 'a/1' and 'c/3' */ - def config = ''' + def config = loadConfig(''' process.executor = 'nope' process.module = 'a/1' - ''' + ''') def script = ''' process hola { 'echo 1' } - + workflow { hola() } ''' - and: - def session = new MockSession(ConfigParserFactory.create().parse(config)) when: - new MockScriptRunner(session).setScript(script).execute() + runScript(script, config) def process = TaskProcessor.currentProcessor() then: @@ -498,8 +447,7 @@ class ScriptRunnerTest extends Dsl2Spec { def 'test resource'() { given: - // -- this represent the configuration file - def config = ''' + def config = loadConfig(''' process { executor = 'nope' queue = 'short' @@ -508,11 +456,12 @@ class ScriptRunnerTest extends Dsl2Spec { penv = 'mpi' memory = '10G' } - ''' + ''') def script = ''' process hola { output: stdout + script: """ queue: ${task.queue} cpus: ${task.cpus} @@ -522,19 +471,14 @@ class ScriptRunnerTest extends Dsl2Spec { memory: ${task.memory} """ } - - workflow { - main: hola() - emit: hola.out + + workflow { + hola() } ''' - and: - def session = new MockSession(ConfigParserFactory.create().parse(config)) when: - def result = new MockScriptRunner(session) - .setScript(script) - .execute() + def result = runScript(script, config) .getVal() .toString() .stripIndent() @@ -567,14 +511,14 @@ class ScriptRunnerTest extends Dsl2Spec { def script = ''' process hola { output: stdout + script: """ cpus: ${task.cpus} """ } - - workflow { - main: hola() - emit: hola.out + + workflow { + hola() } ''' @@ -582,9 +526,7 @@ class ScriptRunnerTest extends Dsl2Spec { def config = [process: [executor:'nope']] when: - def result = new MockScriptRunner(config) - .setScript(script) - .execute() + def result = runScript(script, config) .getVal() .toString() .stripIndent() @@ -604,20 +546,20 @@ class ScriptRunnerTest extends Dsl2Spec { def 'should parse mem and duration units' () { given: - def script = ''' - def result = [:] + def script = ''' + def result = [:] result.mem1 = 1.GB result.mem2 = 1_000_000.toMemory() result.mem3 = MemoryUnit.of(2_000) result.time1 = 2.hours result.time2 = 60_000.toDuration() result.time3 = Duration.of(120_000) - result.flag = 10000 < 1.GB + result.flag = 10000 < 1.GB result // return result object ''' when: - def result = new MockScriptRunner().setScript(script).execute() + def result = runScript(script) then: result.mem1 instanceof MemoryUnit result.mem1 == MemoryUnit.of('1 GB') @@ -634,23 +576,25 @@ class ScriptRunnerTest extends Dsl2Spec { given: def script = ''' - X = 10 process taskHello { maxRetries -1 maxErrors -X + input: + val X + script: 'echo hello' } - - workflow { taskHello() } + + workflow { taskHello(10) } ''' when: - def result = new MockScriptRunner().setScript(script).execute() + def result = runScript(script) def processor = TaskProcessor.currentProcessor() then: processor.config.maxRetries == -1 - processor.config.maxErrors == -10 + Bolts.resolveLazy([X: 10], processor.config.maxErrors) == -10 } @@ -661,109 +605,31 @@ class ScriptRunnerTest extends Dsl2Spec { /* * the module defined in the config file 'b/2' has priority and overrides the 'a/1' and 'c/3' */ - def config = ''' + def config = loadConfig(''' process.executor = 'nope' stubRun = true - ''' + ''') def script = ''' process hola { output: - stdout - stub: - /echo foo/ - script: - /echo bar/ - } - - workflow { - main: hola() - emit: hola.out - } - ''' - - and: - def session = new MockSession(ConfigParserFactory.create().parse(config)) - - when: - def result = new MockScriptRunner(session).setScript(script).execute() - - // when no outputs are specified, the 'stdout' is the default output - then: - result instanceof DataflowVariable - result.val == "echo foo" - - } - - def 'test stub after script'() { - - given: - /* - * the module defined in the config file 'b/2' has priority and overrides the 'a/1' and 'c/3' - */ - def config = ''' - process.executor = 'nope' - stubRun = true - ''' + stdout - def script = ''' - process hola { - output: - stdout script: - /echo bar/ - stub: - /echo foo/ - } - - workflow { main: hola(); emit: hola.out } - ''' - - and: - def session = new MockSession(ConfigParserFactory.create().parse(config)) - - when: - def result = new MockScriptRunner(session).setScript(script).execute() - - // when no outputs are specified, the 'stdout' is the default output - then: - result instanceof DataflowVariable - result.val == "echo foo" - - } - - def 'test stub only script'() { + /echo bar/ - given: - /* - * the module defined in the config file 'b/2' has priority and overrides the 'a/1' and 'c/3' - */ - def config = ''' - process.executor = 'nope' - stubRun = true - ''' - - def script = ''' - process hola { - output: - stdout stub: - /echo foo/ + /echo foo/ } - - workflow { - main: hola() - emit: hola.out + + workflow { + hola() } ''' - and: - def session = new MockSession(ConfigParserFactory.create().parse(config)) - when: - def result = new MockScriptRunner(session).setScript(script).execute() + def result = runScript(script, config) - // when no outputs are specified, the 'stdout' is the default output then: result instanceof DataflowVariable result.val == "echo foo" diff --git a/modules/nextflow/src/test/groovy/nextflow/script/WorkflowDefTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/WorkflowDefTest.groovy index 4607a45619..08163e8610 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/WorkflowDefTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/WorkflowDefTest.groovy @@ -1,18 +1,13 @@ package nextflow.script -import spock.lang.Timeout - import groovy.util.logging.Slf4j import groovyx.gpars.dataflow.DataflowQueue import groovyx.gpars.dataflow.DataflowVariable -import nextflow.Session -import nextflow.ast.NextflowDSL -import org.codehaus.groovy.control.CompilerConfiguration -import org.codehaus.groovy.control.MultipleCompilationErrorsException -import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer -import org.codehaus.groovy.runtime.typehandling.GroovyCastException +import nextflow.exception.ScriptCompilationException +import spock.lang.Timeout import test.Dsl2Spec -import test.MockScriptRunner + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso @@ -21,56 +16,33 @@ import test.MockScriptRunner @Timeout(5) class WorkflowDefTest extends Dsl2Spec { - static abstract class TestScript extends BaseScript { - - private injectSession() { - try { - def sess = binding.getSession() - if( !sess ) - return - def f = this.class.superclass.superclass.getDeclaredField('session') - f.setAccessible(true) - f.set(this, sess) - } - catch (GroovyCastException e) { - log.warn "Can't inject session -- not a ScriptBinding context object" - } - } - - Object run() { - injectSession() - runScript() - return this - } - - } - - def 'should parse workflow' () { + def 'should define named workflows' () { given: - def config = new CompilerConfiguration() - config.setScriptBaseClass(TestScript.class.name) - config.addCompilationCustomizers( new ASTTransformationCustomizer(NextflowDSL)) - def SCRIPT = ''' - + workflow alpha { print 'Hello world' } - - workflow bravo() { - take: foo - take: bar + + workflow bravo { + take: + foo + bar + main: - print foo - print bar - emit: - foo+bar + print foo + print bar + + emit: + foo+bar } - - workflow delta() { - take: foo - take: bar + + workflow delta { + take: + foo + bar + main: println foo+bar } @@ -79,45 +51,46 @@ class WorkflowDefTest extends Dsl2Spec { ''' when: - def script = (TestScript)new GroovyShell(new ScriptBinding(), config).parse(SCRIPT).run() + def script = loadScript(SCRIPT, module: true) def meta = ScriptMeta.get(script) then: meta.definitions.size() == 4 meta.getWorkflow('alpha') .declaredInputs == [] - meta.getWorkflow('alpha') .declaredVariables == [] meta.getWorkflow('alpha') .source.stripIndent(true) == "print 'Hello world'\n" meta.getWorkflow('bravo') .declaredInputs == ['foo', 'bar'] - meta.getWorkflow('bravo') .declaredVariables == ['$out0'] meta.getWorkflow('bravo') .source.stripIndent(true) == '''\ - take: foo - take: bar + take: + foo + bar + main: - print foo - print bar - emit: - foo+bar + print foo + print bar + + emit: + foo+bar '''.stripIndent(true) meta.getWorkflow('delta') .declaredInputs == ['foo','bar'] - meta.getWorkflow('delta') .declaredVariables == [] meta.getWorkflow('delta') .source.stripIndent(true) == '''\ - take: foo - take: bar + take: + foo + bar + main: println foo+bar '''.stripIndent(true) meta.getWorkflow('empty') .source == '' meta.getWorkflow('empty') .declaredInputs == [] - meta.getWorkflow('empty') .declaredVariables == [] } - def 'should define anonymous workflow' () { + def 'should define entry workflow' () { def SCRIPT = ''' - + workflow { print 1 print 2 @@ -125,8 +98,8 @@ class WorkflowDefTest extends Dsl2Spec { ''' when: - def runner = new MockScriptRunner().setScript(SCRIPT).invoke() - def meta = ScriptMeta.get(runner.getScript()) + def script = loadScript(SCRIPT) + def meta = ScriptMeta.get(script) then: meta.getWorkflow(null).getSource().stripIndent(true) == 'print 1\nprint 2\n' @@ -135,25 +108,18 @@ class WorkflowDefTest extends Dsl2Spec { def 'should run workflow block' () { given: - def config = new CompilerConfiguration() - config.setScriptBaseClass(TestScript.class.name) - config.addCompilationCustomizers( new ASTTransformationCustomizer(NextflowDSL)) - def SCRIPT = ''' - + workflow alpha { take: foo - emit: bar - emit: baz - - main: "$x world" + main: bar = foo ; baz = foo + emit: bar ; baz } - + ''' when: - def binding = new ScriptBinding().setSession(Mock(Session)) - def script = (TestScript)new GroovyShell(binding,config).parse(SCRIPT).run() + def script = loadScript(SCRIPT, module: true) def workflow = ScriptMeta.get(script).getWorkflow('alpha') then: workflow.declaredInputs == ['foo'] @@ -164,27 +130,21 @@ class WorkflowDefTest extends Dsl2Spec { def 'should report malformed workflow block' () { given: - def config = new CompilerConfiguration() - config.setScriptBaseClass(TestScript.class.name) - config.addCompilationCustomizers( new ASTTransformationCustomizer(NextflowDSL)) - def SCRIPT = ''' - + workflow alpha { take: foo main: println foo take: bar } - + ''' when: - def binding = new ScriptBinding().setSession(Mock(Session)) - def script = (TestScript)new GroovyShell(binding,config).parse(SCRIPT).run() - def workflow = ScriptMeta.get(script).getWorkflow('alpha') + loadScript(SCRIPT) then: - def e = thrown(MultipleCompilationErrorsException) - e.message.contains('Unexpected workflow `take` context here') + def e = thrown(ScriptCompilationException) + e.cause.message.contains('Invalid workflow definition') } @@ -194,16 +154,16 @@ class WorkflowDefTest extends Dsl2Spec { // does NOT define an implicit `it` parameter that would clash // with the `it` used by the inner closure - def SCRIPT = """ - + def SCRIPT = """ + workflow { - Channel.empty().map { id -> id +1 } - Channel.empty().map { it -> def id = it+1 } + channel.empty().map { id -> id +1 } + channel.empty().map { it -> def id = it+1 } } """ when: - new MockScriptRunner().setScript(SCRIPT).execute() + runScript(SCRIPT) then: noExceptionThrown() @@ -268,50 +228,40 @@ class WorkflowDefTest extends Dsl2Spec { def 'should capture workflow code' () { given: - def config = new CompilerConfiguration() - config.setScriptBaseClass(TestScript.class.name) - config.addCompilationCustomizers( new ASTTransformationCustomizer(NextflowDSL)) - def SCRIPT = ''' - + workflow alpha { take: foo main: - print x - emit: - foo + print 'x' + emit: + foo } ''' when: - def binding = new ScriptBinding().setSession(Mock(Session)) - def script = (TestScript)new GroovyShell(binding,config).parse(SCRIPT).run() + def script = loadScript(SCRIPT, module: true) def workflow = ScriptMeta.get(script).getWorkflow('alpha') then: workflow.getSource().stripIndent(true) == '''\ take: foo main: - print x - emit: - foo + print 'x' + emit: + foo '''.stripIndent(true) } def 'should capture empty workflow code' () { given: - def config = new CompilerConfiguration() - config.setScriptBaseClass(TestScript.class.name) - config.addCompilationCustomizers( new ASTTransformationCustomizer(NextflowDSL)) - def SCRIPT = ''' - workflow foo { } + workflow foo { } ''' when: - def binding = new ScriptBinding().setSession(Mock(Session)) - def script = (TestScript)new GroovyShell(binding,config).parse(SCRIPT).run() + def script = loadScript(SCRIPT, module: true) def workflow = ScriptMeta.get(script).getWorkflow('foo') then: workflow.getSource() == '' diff --git a/modules/nextflow/src/test/groovy/nextflow/script/params/CmdEvalParamTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/params/CmdEvalParamTest.groovy index 39cbc26322..aca8d7e516 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/params/CmdEvalParamTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/params/CmdEvalParamTest.groovy @@ -16,9 +16,9 @@ package nextflow.script.params -import static test.TestParser.* - import test.Dsl2Spec + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso @@ -29,18 +29,22 @@ class CmdEvalParamTest extends Dsl2Spec { setup: def text = ''' process hola { + input: + val tool + output: - eval 'foo --version' + eval 'foo --version' eval "$params.cmd --help" - eval "$tool --test" - - /echo command/ + eval "$tool --test" + + script: + /echo command/ } - - workflow { hola() } + + workflow { hola('other') } ''' - def binding = [params:[cmd:'bar'], tool: 'other'] + def binding = [params:[cmd:'bar']] def process = parseAndReturnProcess(text, binding) when: @@ -56,7 +60,7 @@ class CmdEvalParamTest extends Dsl2Spec { outs[1].getTarget(binding) == 'bar --help' and: outs[2].getName() =~ /nxf_out_eval_\d+/ - outs[2].getTarget(binding) == 'other --test' + outs[2].getTarget(binding + [tool: 'other']) == 'other --test' } } diff --git a/modules/nextflow/src/test/groovy/nextflow/script/params/EnvOutParamTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/params/EnvOutParamTest.groovy index ece5f8bbfb..552c929839 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/params/EnvOutParamTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/params/EnvOutParamTest.groovy @@ -16,9 +16,9 @@ package nextflow.script.params -import static test.TestParser.* - import test.Dsl2Spec + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso @@ -26,45 +26,17 @@ import test.Dsl2Spec class EnvOutParamTest extends Dsl2Spec { def 'should define env outputs' () { - setup: - def text = ''' - process hola { - output: - env FOO - env BAR - - /echo command/ - } - - workflow { hola() } - ''' - - def binding = [:] - def process = parseAndReturnProcess(text, binding) - - when: - def outs = process.config.getOutputs() as List - - then: - outs.size() == 2 - and: - outs[0].name == 'FOO' - and: - outs[1].name == 'BAR' - - } - - def 'should define env outputs with quotes' () { setup: def text = ''' process hola { output: env 'FOO' env 'BAR' - - /echo command/ + + script: + /echo command/ } - + workflow { hola() } ''' @@ -88,12 +60,13 @@ class EnvOutParamTest extends Dsl2Spec { def text = ''' process hola { output: - env FOO, optional: false - env BAR, optional: true + env 'FOO', optional: false + env 'BAR', optional: true + script: /echo command/ } - + workflow { hola() } ''' @@ -121,10 +94,11 @@ class EnvOutParamTest extends Dsl2Spec { process hola { output: env { 0 } - - /echo command/ + + script: + /echo command/ } - + workflow { hola() } ''' diff --git a/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsDsl2Test.groovy b/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsDsl2Test.groovy index 3a742a25de..a11ecbfde2 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsDsl2Test.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsDsl2Test.groovy @@ -1,15 +1,10 @@ package nextflow.script.params -import nextflow.Session -import nextflow.ast.NextflowDSL -import nextflow.script.BaseScript -import nextflow.script.ScriptBinding import nextflow.script.ScriptMeta -import org.codehaus.groovy.control.CompilerConfiguration -import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer import spock.lang.Timeout import test.Dsl2Spec -import test.MockScriptRunner + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso @@ -17,124 +12,29 @@ import test.MockScriptRunner @Timeout(5) class ParamsDsl2Test extends Dsl2Spec { - def 'should not allow unqualified input file' () { - given: - def SCRIPT = ''' - - process foo { - input: - tuple 'x' - /touch x/ - } - - workflow { - foo() - } - ''' - - when: - new MockScriptRunner() .setScript(SCRIPT).execute() - then: - def e = thrown(IllegalArgumentException) - e.message == "Unqualified input file declaration is not allowed - replace `tuple 'x',..` with `tuple path('x'),..`" - } + def 'should allow unqualified stdin and stdout' () { - def 'should not allow unqualified input val' () { given: def SCRIPT = ''' - - process foo { - input: - tuple X - /echo $X/ - } - - workflow { - foo() - } - ''' - when: - new MockScriptRunner() .setScript(SCRIPT).execute() - then: - def e = thrown(IllegalArgumentException) - e.message == "Unqualified input value declaration is not allowed - replace `tuple X,..` with `tuple val(X),..`" - } + process alpha { + input: + stdin + output: + stdout - - def 'should not allow unqualified output file' () { - given: - def SCRIPT = ''' - - process foo { - output: - tuple 'x' - /touch x/ + script: + /echo foo/ } - - workflow { - foo() - } - ''' - - when: - new MockScriptRunner() .setScript(SCRIPT).execute() - then: - def e = thrown(IllegalArgumentException) - e.message == "Unqualified output path declaration is not allowed - replace `tuple 'x',..` with `tuple path('x'),..`" - } - def 'should not allow unqualified output value' () { - given: - def SCRIPT = ''' - - process foo { - output: - tuple X - /echo hello/ - } - - workflow { - foo() - } + workflow {} ''' when: - new MockScriptRunner() .setScript(SCRIPT).execute() - then: - def e = thrown(IllegalArgumentException) - e.message == "Unqualified output value declaration is not allowed - replace `tuple X,..` with `tuple val(X),..`" - } - - - def 'should allow unqualified stdin and stdout' () { - - given: - def session = new Session() + def script = loadScript(SCRIPT) and: - def config = new CompilerConfiguration() - config.setScriptBaseClass(BaseScript.class.name) - config.addCompilationCustomizers( new ASTTransformationCustomizer(NextflowDSL)) - - def SCRIPT = ''' - - process alpha { - input: - stdin - output: - stdout - - /echo foo/ - } - - workflow { true } - ''' - - when: - def binding = new ScriptBinding().setSession(session) - def script = (BaseScript)new GroovyShell(binding,config).parse(SCRIPT); script.run() - and: - def process = ScriptMeta.get(script).getProcess('alpha'); process.initialize() + def process = ScriptMeta.get(script).getProcess('alpha') + process.initialize() then: def inputs = process.processConfig.getInputs() @@ -151,31 +51,26 @@ class ParamsDsl2Test extends Dsl2Spec { def 'should allow unqualified tuple stdin and stdout' () { given: - def session = new Session() - and: - def config = new CompilerConfiguration() - config.setScriptBaseClass(BaseScript.class.name) - config.addCompilationCustomizers( new ASTTransformationCustomizer(NextflowDSL)) - def SCRIPT = ''' - - process beta { - input: - tuple stdin, val(x) - output: - tuple stdout, path('z') - - /echo foo/ - } - - workflow { true } + + process beta { + input: + tuple stdin, val(x) + output: + tuple stdout, path('z') + + script: + /echo foo/ + } + + workflow {} ''' when: - def binding = new ScriptBinding().setSession(session) - def script = (BaseScript)new GroovyShell(binding,config).parse(SCRIPT); script.run() + def script = loadScript(SCRIPT) and: - def process = ScriptMeta.get(script).getProcess('beta'); process.initialize() + def process = ScriptMeta.get(script).getProcess('beta') + process.initialize() then: def inputs = process.processConfig.getInputs() diff --git a/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsInTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsInTest.groovy index 5daabf87bd..68bf455a1c 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsInTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsInTest.groovy @@ -16,16 +16,15 @@ package nextflow.script.params -import static test.TestParser.* - import java.nio.file.Paths import groovyx.gpars.dataflow.DataflowQueue import groovyx.gpars.dataflow.DataflowVariable import nextflow.Channel -import nextflow.processor.TaskProcessor import spock.lang.Timeout import test.Dsl2Spec + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso @@ -40,22 +39,19 @@ class ParamsInTest extends Dsl2Spec { def testInputVals() { setup: def text = ''' - x = 'Hello' - y = 'Hola' - process hola { input: + val w val x - val x - val x - val x + val y + val z - return '' + script: + '' } - + workflow { - def z = channel.fromList([1,2]) - hola(x, y, 'ciao', z) + hola('Hello', 'Hola', 'ciao', channel.of(1, 2)) } ''' @@ -70,7 +66,7 @@ class ParamsInTest extends Dsl2Spec { process.config.getInputs().size() == 4 in1.class == ValueInParam - in1.name == 'x' + in1.name == 'w' in1.inChannel.val == 'Hello' in2.class == ValueInParam @@ -78,11 +74,11 @@ class ParamsInTest extends Dsl2Spec { in2.inChannel.val == 'Hola' in3.class == ValueInParam - in3.name == 'x' + in3.name == 'y' in3.inChannel.val == 'ciao' in4.class == ValueInParam - in4.name == 'x' + in4.name == 'z' in4.inChannel.val == 1 in4.inChannel.val == 2 in4.inChannel.val == Channel.STOP @@ -93,28 +89,29 @@ class ParamsInTest extends Dsl2Spec { setup: def text = ''' - A = 3 - B = 4 - process hola { input: val x val y val z - return '' + script: + '' } - + workflow { + A = 3 + B = 4 + def x = channel.of(1,2) - def y = channel.of('a', 'b') + def y = channel.of('a', 'b') def z = channel.of(A, B) hola(x, y, z) } ''' when: - TaskProcessor process = parseAndReturnProcess(text) + def process = parseAndReturnProcess(text) def in1 = process.config.getInputs().get(0) def in2 = process.config.getInputs().get(1) def in3 = process.config.getInputs().get(2) @@ -140,18 +137,19 @@ class ParamsInTest extends Dsl2Spec { def testInputFiles() { setup: def text = ''' - x = java.nio.file.Paths.get('file.x') - process hola { input: file x file f1 file 'file.txt' - return '' + script: + '' } - + workflow { + x = file('file.x') + hola(x, x, x) } ''' @@ -167,88 +165,49 @@ class ParamsInTest extends Dsl2Spec { in1.name == 'x' in1.filePattern == '*' - in1.inChannel.val == Paths.get('file.x') + in1.inChannel.val.name == 'file.x' in1.index == 0 in2.name == 'f1' in2.filePattern == '*' - in2.inChannel.val == Paths.get('file.x') + in2.inChannel.val.name == 'file.x' in2.index == 1 in3.name == 'file.txt' in3.filePattern == 'file.txt' - in3.inChannel.val == Paths.get('file.x') + in3.inChannel.val.name == 'file.x' in3.index == 2 } def testInputFilesWithGString() { setup: - def ctx = [x: 'main.txt', y: 'hello'] + def ctx = [id: 'hello'] def text = ''' - q = java.nio.file.Paths.get('file.txt') - process hola { input: - file "$x" - file "${y}.txt" + val id + file "${id}.txt" - return '' - } - - workflow { - hola(q, "str") + script: + '' } - ''' - - when: - def process = parseAndReturnProcess(text) - FileInParam in1 = process.config.getInputs().get(0) - FileInParam in2 = process.config.getInputs().get(1) - - then: - process.config.getInputs().size() == 2 - - in1.name == '__$fileinparam<0>' - in1.getFilePattern(ctx) == 'main.txt' - in1.inChannel.val == Paths.get('file.txt') - in2.name == '__$fileinparam<1>' - in2.getFilePattern(ctx) == 'hello.txt' - in2.inChannel.val == "str" - - } - - def testInputFilesWithClosure() { - setup: - def ctx = [x: 'main.txt', y: 'hello'] - def text = ''' - q = java.nio.file.Paths.get('file.txt') - - process hola { - input: - file "$x" - file "${y}.txt" - - return '' - } - workflow { - hola(q, "str") + hola('hello', 'str') } ''' when: def process = parseAndReturnProcess(text) - FileInParam in1 = process.config.getInputs().get(0) - FileInParam in2 = process.config.getInputs().get(1) + def in1 = process.config.getInputs().get(0) + def in2 = process.config.getInputs().get(1) then: process.config.getInputs().size() == 2 - in1.name == '__$fileinparam<0>' - in1.getFilePattern(ctx) == 'main.txt' - in1.inChannel.val == Paths.get('file.txt') + in1.name == 'id' + in1.inChannel.val == 'hello' in2.name == '__$fileinparam<1>' in2.getFilePattern(ctx) == 'hello.txt' @@ -259,18 +218,19 @@ class ParamsInTest extends Dsl2Spec { def testFromStdin() { setup: def text = ''' - x = 'Hola mundo' - y = 'Ciao mondo' - process hola { input: stdin stdin - return '' + script: + '' } - + workflow { + x = 'Hola mundo' + y = 'Ciao mondo' + hola(x, y) } ''' @@ -295,18 +255,19 @@ class ParamsInTest extends Dsl2Spec { def testInputEnv() { setup: def text = ''' - x = 'aaa' - y = channel.of(1,2) - process hola { input: - env VAR_X + env 'VAR_X' env 'VAR_Y' - return '' + script: + '' } - + workflow { + x = 'aaa' + y = channel.of(1,2) + hola(x, y) } ''' @@ -331,173 +292,19 @@ class ParamsInTest extends Dsl2Spec { } - - def testInputMap() { - setup: - def text = ''' - x = 'Hola mundo' - - process hola { - input: - tuple val(p) - tuple val(p), val(q) - tuple val(v), file('file_name.fa') - tuple val(p), file('file_name.txt'), stdin - tuple val(t), path(file, name:'file.fa') - - return '' - } - - workflow { - hola(x, x, 'str', 'ciao', 0) - } - ''' - - when: - def process = parseAndReturnProcess(text) - TupleInParam in1 = process.config.getInputs().get(0) - TupleInParam in2 = process.config.getInputs().get(1) - TupleInParam in3 = process.config.getInputs().get(2) - TupleInParam in4 = process.config.getInputs().get(3) - TupleInParam in5 = process.config.getInputs().get(4) - - then: - process.config.getInputs().size() == 5 - - in1.inner.size() == 1 - in1.inner.get(0) instanceof ValueInParam - in1.inner.get(0).index == 0 - in1.inner.get(0).mapIndex == 0 - in1.inner.get(0).name == 'p' - in1.inChannel.val == 'Hola mundo' - - in2.inner.size() == 2 - in2.inner.get(0) instanceof ValueInParam - in2.inner.get(0).name == 'p' - in2.inner.get(0).index == 1 - in2.inner.get(0).mapIndex == 0 - in2.inner.get(1) instanceof ValueInParam - in2.inner.get(1).name == 'q' - in2.inner.get(1).index == 1 - in2.inner.get(1).mapIndex == 1 - in2.inChannel.val == 'Hola mundo' - - in3.inner.size() == 2 - in3.inner.get(0) instanceof ValueInParam - in3.inner.get(0).name == 'v' - in3.inner.get(0).index == 2 - in3.inner.get(0).mapIndex == 0 - in3.inner.get(1) instanceof FileInParam - in3.inner.get(1).name == 'file_name.fa' - in3.inner.get(1).filePattern == 'file_name.fa' - in3.inner.get(1).index == 2 - in3.inner.get(1).mapIndex == 1 - in3.inChannel.val == 'str' - - in4.inner.size() == 3 - in4.inner.get(0) instanceof ValueInParam - in4.inner.get(0).name == 'p' - in4.inner.get(0).index == 3 - in4.inner.get(0).mapIndex == 0 - in4.inner.get(1) instanceof FileInParam - in4.inner.get(1).name == 'file_name.txt' - in4.inner.get(1).filePattern == 'file_name.txt' - in4.inner.get(1).index == 3 - in4.inner.get(1).mapIndex == 1 - in4.inner.get(2) instanceof StdInParam - in4.inner.get(2).name == '-' - in4.inner.get(2).index == 3 - in4.inner.get(2).mapIndex == 2 - in4.inChannel.val == 'ciao' - - in5.inner.size() == 2 - in5.inner.get(0) instanceof ValueInParam - in5.inner.get(0).name == 't' - in5.inner.get(0).index == 4 - in5.inner.get(0).mapIndex == 0 - in5.inner.get(1) instanceof FileInParam - in5.inner.get(1).name == 'file' - in5.inner.get(1).filePattern == 'file.fa' - in5.inner.get(1).index == 4 - in5.inner.get(1).mapIndex == 1 - in5.inChannel.val == 0 - - } - - def testTupleFileWithGString() { - + def testInputTuple2() { setup: def text = ''' - q = 'the file content' - process hola { input: - tuple file('name_$x') - tuple file("${x}_name.${str}" ) - - tuple file("hola_${x}") - tuple file( handle: "${x}.txt") - - tuple file( { "${x}_name.txt" } ) - tuple file( handle: { "name_${x}.txt" } ) + tuple( val(a), file(x), val(b) ) + tuple( val(p), file('txt'), env('q') ) + tuple( val(v), file(xx:'yy'), stdin, env('W') ) - return '' + script: + '' } - - workflow { - hola(q,q, q,q, q,q) - } - ''' - - when: - def process = parseAndReturnProcess(text) - TupleInParam in0 = process.config.getInputs().get(0) - TupleInParam in1 = process.config.getInputs().get(1) - TupleInParam in2 = process.config.getInputs().get(2) - TupleInParam in3 = process.config.getInputs().get(3) - TupleInParam in4 = process.config.getInputs().get(4) - TupleInParam in5 = process.config.getInputs().get(5) - def ctx = [x:'the_file', str: 'fastq'] - then: - in0.inChannel.val == 'the file content' - in0.inner[0] instanceof FileInParam - (in0.inner[0] as FileInParam).name == 'name_$x' - (in0.inner[0] as FileInParam).getFilePattern(ctx) == 'name_$x' - - in1.inner[0] instanceof FileInParam - (in1.inner[0] as FileInParam).name == '__$fileinparam<1:0>' - (in1.inner[0] as FileInParam).getFilePattern(ctx) == 'the_file_name.fastq' - - in2.inner[0] instanceof FileInParam - (in2.inner[0] as FileInParam).name == '__$fileinparam<2:0>' - (in2.inner[0] as FileInParam).getFilePattern(ctx) == 'hola_the_file' - - in3.inner[0] instanceof FileInParam - (in3.inner[0] as FileInParam).name == 'handle' - (in3.inner[0] as FileInParam).getFilePattern(ctx) == 'the_file.txt' - - in4.inner[0] instanceof FileInParam - (in4.inner[0] as FileInParam).name == '__$fileinparam<4:0>' - (in4.inner[0] as FileInParam).getFilePattern(ctx) == 'the_file_name.txt' - - in5.inner[0] instanceof FileInParam - (in5.inner[0] as FileInParam).name == 'handle' - (in5.inner[0] as FileInParam).getFilePattern(ctx) == 'name_the_file.txt' - } - - def testInputMap2() { - setup: - def text = ''' - process hola { - input: - tuple( val(a), file(x), val(b) ) - tuple( val(p), file('txt'), env('q') ) - tuple( val(v), file(xx:'yy'), stdin, env(W) ) - - return '' - } - workflow { hola(1, 2, 3) } @@ -558,33 +365,24 @@ class ParamsInTest extends Dsl2Spec { setup: def text = ''' - x = 'aaa' - y = [1,2] - process hola { input: each x each p each z - each file(foo) + each file('foo') each file('bar') - return '' + script: + '' } - + workflow { - hola(x, y, q, foo_ch, bar_ch) + hola('aaa', [1, 2], channel.of(1, 2, 3), 'file-a.txt', 'file-x.fa') } ''' when: - - def binding = [:] - binding.q = new DataflowQueue<>() - binding.q << 1 << 2 << 3 << Channel.STOP - binding.foo_ch = 'file-a.txt' - binding.bar_ch = 'file-x.fa' - - def process = parseAndReturnProcess(text, binding) + def process = parseAndReturnProcess(text) def in0 = (EachInParam)process.config.getInputs().get(0) def in1 = (EachInParam)process.config.getInputs().get(1) def in2 = (EachInParam)process.config.getInputs().get(2) @@ -670,8 +468,6 @@ class ParamsInTest extends Dsl2Spec { setup: def FILE = '/data/file.txt' def text = """ - x = '$FILE' - process hola { input: path x, arity: '1' @@ -681,10 +477,12 @@ class ParamsInTest extends Dsl2Spec { path f2, name: '*.fa' path f3, stageAs: '*.txt' - return '' + script: + '' } - + workflow { + x = '$FILE' hola(x, x, x, x, x, x) } """ @@ -740,35 +538,32 @@ class ParamsInTest extends Dsl2Spec { def 'test input paths with gstring'() { setup: - def ctx = [x: 'main.txt', y: 'hello', z:'the_file_name'] + def ctx = [id: 'hello'] def text = ''' - q = 'file.txt' - process hola { input: - path "$x" - path "${y}.txt" + val id + path "${id}.txt" - return '' + script: + '' } - + workflow { - hola(q, 'str') + hola('hello', 'str') } ''' when: def process = parseAndReturnProcess(text) - FileInParam in1 = process.config.getInputs().get(0) - FileInParam in2 = process.config.getInputs().get(1) + def in1 = process.config.getInputs().get(0) + def in2 = process.config.getInputs().get(1) then: process.config.getInputs().size() == 2 - in1.name == '__$pathinparam<0>' - in1.getFilePattern(ctx) == 'main.txt' - in1.inChannel.val == 'file.txt' - in1.isPathQualifier() + in1.name == 'id' + in1.inChannel.val == 'hello' in2.name == '__$pathinparam<1>' in2.getFilePattern(ctx) == 'hello.txt' @@ -776,42 +571,37 @@ class ParamsInTest extends Dsl2Spec { in2.isPathQualifier() } - def 'test set path with gstring'() { + def 'test tuple path with gstring'() { setup: def text = ''' - q = '/the/file/path' - process hola { input: - tuple path("hola_${x}") - tuple path({ "${x}_name.txt" }) + tuple val(id), path("hola_${id}") - return '' + script: + '' } - + workflow { - hola(q, q) + hola(['the_file', '/the/file/path']) } ''' when: def process = parseAndReturnProcess(text) TupleInParam in1 = process.config.getInputs().get(0) - TupleInParam in2 = process.config.getInputs().get(1) - def ctx = [x:'the_file', str: 'fastq'] + def ctx = [id:'the_file'] then: - in1.inChannel.val == '/the/file/path' - in1.inner[0] instanceof FileInParam - (in1.inner[0] as FileInParam).getName() == '__$pathinparam<0:0>' - (in1.inner[0] as FileInParam).getFilePattern(ctx) == 'hola_the_file' - (in1.inner[0] as FileInParam).isPathQualifier() + in1.inChannel.val == ['the_file', '/the/file/path'] + in1.inner[0] instanceof ValueInParam + (in1.inner[0] as ValueInParam).getName() == 'id' - in2.inner[0] instanceof FileInParam - (in2.inner[0] as FileInParam).name == '__$pathinparam<1:0>' - (in2.inner[0] as FileInParam).getFilePattern(ctx) == 'the_file_name.txt' - (in2.inner[0] as FileInParam).isPathQualifier() + in1.inner[1] instanceof FileInParam + (in1.inner[1] as FileInParam).name == '__$pathinparam<0:1>' + (in1.inner[1] as FileInParam).getFilePattern(ctx) == 'hola_the_file' + (in1.inner[1] as FileInParam).isPathQualifier() } @@ -820,13 +610,14 @@ class ParamsInTest extends Dsl2Spec { def text = ''' process hola { input: - tuple( val(a), path(x) ) + tuple( val(a), path(x) ) tuple( val(p), path('txt') ) tuple( val(v), path(xx, stageAs: 'yy') ) - return '' + script: + '' } - + workflow { hola(1,2,3) } @@ -881,12 +672,13 @@ class ParamsInTest extends Dsl2Spec { process hola { input: - each path(foo) - each path('bar') + each path('foo') + each path('bar') - return '' + script: + '' } - + workflow { hola('file-a.txt', 'file-x.fa') } @@ -928,17 +720,17 @@ class ParamsInTest extends Dsl2Spec { setup: def text = ''' - ch = 'something' - process hola { input: - val x - tuple val(x), file(x) + val x + tuple val(y), file(z) + script: /command/ } - + workflow { + ch = 'something' hola(ch, ch) } ''' diff --git a/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsOutTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsOutTest.groovy index ed18f2faf9..8c31be7f8a 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsOutTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/params/ParamsOutTest.groovy @@ -16,8 +16,6 @@ package nextflow.script.params -import static test.TestParser.* - import java.nio.file.Path import groovyx.gpars.dataflow.DataflowVariable @@ -25,6 +23,8 @@ import nextflow.processor.TaskContext import nextflow.script.TokenVar import nextflow.util.BlankSeparatedList import test.Dsl2Spec + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso @@ -57,14 +57,18 @@ class ParamsOutTest extends Dsl2Spec { val x val p val 10 - val 'str' + val 'str' val { x } val "${y}" - val x.y + val x.y - return '' + script: + x = 'x' + p = 'p' + y = 'y' + '' } - + workflow { hola() } @@ -123,11 +127,14 @@ class ParamsOutTest extends Dsl2Spec { def text = ''' process foo { output: - val one + val one file 'two' - return '' + + script: + one = 'one' + '' } - + workflow { foo() } ''' @@ -159,9 +166,12 @@ class ParamsOutTest extends Dsl2Spec { file 'y' file p - return '' + script: + x = 'x' + p = 'p' + '' } - + workflow { hola() } ''' @@ -199,24 +209,30 @@ class ParamsOutTest extends Dsl2Spec { process hola { output: - file "${x}_name" - file "${x}_${y}.fa" - file "simple.txt" - file "${z}.txt:sub/dir/${x}.fa" - tuple file("${z}.txt:${x}.fa") - tuple path("${z}.txt:${x}.fa") - file meta.id - file "$meta.id" - return '' + file "${x}_name" + file "${x}_${y}.fa" + file "simple.txt" + file "${z}.txt:sub/dir/${x}.fa" + tuple file("${z}.txt:${x}.fa") + tuple path("${z}.txt:${x}.fa") + file meta.id + file "$meta.id" + + script: + x = 'x' + y = 'y' + z = 'z' + meta = [:] + '' } - + workflow { hola() } ''' def binding = [x: 'hola', y:99, z:'script_file', meta: [id:'hello.txt']] def process = parseAndReturnProcess(text, binding) def ctx = binding - + when: FileOutParam out0 = process.config.getOutputs().get(0) FileOutParam out1 = process.config.getOutputs().get(1) @@ -283,9 +299,12 @@ class ParamsOutTest extends Dsl2Spec { file(x) tuple file(y) - return '' + script: + x = 'x' + y = 'y' + '' } - + workflow { hola() } ''' @@ -320,9 +339,15 @@ class ParamsOutTest extends Dsl2Spec { file "$v" file 'w' - return '' + script: + x = 'hola' + y = 'hola_2' + z = 'hola_z' + u = 'file_u' + v = 'file_v' + '' } - + workflow { hola() } ''' @@ -371,13 +396,17 @@ class ParamsOutTest extends Dsl2Spec { process hola { output: - file { "${x}_name" } - file { "${params.fileName}_${y}.fa" } - tuple file({ "${z}.txt" }) - - return '' + file { "${x}_name" } + file { "${params.fileName}_${y}.fa" } + tuple file({ "${z}.txt" }) + + script: + x = 'hola' + y = 99 + z = 'script_file' + '' } - + workflow { hola() } ''' @@ -414,23 +443,25 @@ class ParamsOutTest extends Dsl2Spec { process hola { output: - file x + file x path x, maxDepth: 5 - path x, hidden: true - path x, followLinks: false - path x, type: 'file' - path x, separatorChar: '#' - - path x, hidden: false - path x, followLinks: true - path x, type: 'dir' - path x, glob: false - path x, optional: true - - return '' + path x, hidden: true + path x, followLinks: false + path x, type: 'file' + path x, separatorChar: '#' + + path x, hidden: false + path x, followLinks: true + path x, type: 'dir' + path x, glob: false + path x, optional: true + + script: + x = 'x' + '' } - + workflow { hola() } ''' @@ -471,133 +502,6 @@ class ParamsOutTest extends Dsl2Spec { } - def testTupleOutParams() { - - setup: - def text = ''' - process hola { - output: - tuple val(x) - tuple val(y), stdout, file('*.fa') - tuple stdout, val(z) - - return '' - } - - workflow { hola() } - ''' - - def binding = [:] - def process = parseAndReturnProcess(text, binding) - - when: - TupleOutParam out1 = process.config.getOutputs().get(0) - TupleOutParam out2 = process.config.getOutputs().get(1) - TupleOutParam out3 = process.config.getOutputs().get(2) - - then: - process.config.getOutputs().size() == 3 - - out1.outChannel instanceof DataflowVariable - out1.inner.size() == 1 - out1.inner[0] instanceof ValueOutParam - out1.inner[0].name == 'x' - out1.inner[0].index == 0 - - out2.outChannel instanceof DataflowVariable - out2.inner[0] instanceof ValueOutParam - out2.inner[0].name == 'y' - out2.inner[0].index == 1 - out2.inner[1] instanceof StdOutParam - out2.inner[1].name == '-' - out2.inner[1].index == 1 - out2.inner[2] instanceof FileOutParam - out2.inner[2].name == null - out2.inner[2].filePattern == '*.fa' - out2.inner[2].index == 1 - out2.inner.size() ==3 - - out3.outChannel instanceof DataflowVariable - out3.inner.size() == 2 - out3.inner[0] instanceof StdOutParam - out3.inner[0].name == '-' - out3.inner[0].index == 2 - out3.inner[1] instanceof ValueOutParam - out3.inner[1].name == 'z' - out3.inner[1].index == 2 - - } - - def testTupleOutValues() { - - setup: - def text = ''' - process hola { - output: - tuple val(x), val('x') - tuple val(1), val('2') - tuple val("$foo"), val { bar } - - return '' - } - - workflow { hola() } - ''' - - def binding = [:] - def process = parseAndReturnProcess(text, binding) - - when: - TupleOutParam out0 = process.config.getOutputs().get(0) - TupleOutParam out1 = process.config.getOutputs().get(1) - TupleOutParam out2 = process.config.getOutputs().get(2) - - then: - process.config.getOutputs().size() == 3 - - // first tuple - out0.outChannel instanceof DataflowVariable - out0.inner.size() == 2 - // val(x) - out0.inner[0] instanceof ValueOutParam - out0.inner[0].index == 0 - out0.inner[0].resolve([x: 'hello']) == 'hello' - // val('x') - out0.inner[1] instanceof ValueOutParam - out0.inner[1].name == null - out0.inner[1].index == 0 - out0.inner[1].resolve([:]) == 'x' - - // -- second tuple - out1.outChannel instanceof DataflowVariable - out1.inner.size() == 2 - // val(1) - out1.inner[0] instanceof ValueOutParam - out1.inner[0].name == null - out1.inner[0].index == 1 - out1.inner[0].resolve([:]) == 1 - // val('2') - out1.inner[1] instanceof ValueOutParam - out1.inner[1].name == null - out1.inner[1].index == 1 - out1.inner[1].resolve([:]) == '2' - - // -- third tuple - out2.outChannel instanceof DataflowVariable - out2.inner.size() == 2 - // val("$foo") - out2.inner[0] instanceof ValueOutParam - out2.inner[0].name == null - out2.inner[0].index == 2 - out2.inner[0].resolve([foo:'hello', bar:'world']) == 'hello' - // val { bar } - out2.inner[1] instanceof ValueOutParam - out2.inner[1].name == null - out2.inner[1].index == 2 - out2.inner[1].resolve([foo:'hello', bar:'world']) == 'world' - - } - def testStdOut() { setup: @@ -606,9 +510,10 @@ class ParamsOutTest extends Dsl2Spec { output: stdout - return '' + script: + '' } - + workflow { hola() } ''' @@ -709,13 +614,15 @@ class ParamsOutTest extends Dsl2Spec { def text = ''' process foo { output: - path x + path x path 'hello.*' path 'hello.txt' - - return '' + + script: + x = 'x' + '' } - + workflow { foo() } ''' @@ -744,7 +651,7 @@ class ParamsOutTest extends Dsl2Spec { out2.getFilePattern() == 'hello.txt' out2.getOutChannel() instanceof DataflowVariable out2.isPathQualifier() - + } @@ -755,14 +662,17 @@ class ParamsOutTest extends Dsl2Spec { process hola { output: - path "${x}_name" - path "${x}_${y}.fa" - path "simple.txt" - path "data/sub/dir/file:${x}.fa" - - return '' + path "${x}_name" + path "${x}_${y}.fa" + path "simple.txt" + path "data/sub/dir/file:${x}.fa" + + script: + x = 'hola' + y = 99 + '' } - + workflow { hola() } ''' @@ -812,13 +722,16 @@ class ParamsOutTest extends Dsl2Spec { process hola { output: path "${x}_name", emit: aaa, topic: 'foo' - path "${x}_${y}.fa", emit: bbb - path "simple.txt", emit: ccc - path "data/sub/dir/file:${x}.fa", emit: ddd - - return '' + path "${x}_${y}.fa", emit: bbb + path "simple.txt", emit: ccc + path "data/sub/dir/file:${x}.fa", emit: ddd + + script: + x = 'hola' + y = 99 + '' } - + workflow { hola() } ''' @@ -871,14 +784,18 @@ class ParamsOutTest extends Dsl2Spec { process hola { output: - tuple path(x) - tuple path(y) - tuple path("sample.fa") - tuple path("data/file:${q}.fa") - - return '' + tuple path(x) + tuple path(y) + tuple path("sample.fa") + tuple path("data/file:${q}.fa") + + script: + x = 'x' + y = 'y' + q = 'q' + '' } - + workflow { hola() } ''' @@ -924,7 +841,7 @@ class ParamsOutTest extends Dsl2Spec { process foo { output: - path x, + path x, maxDepth:2, hidden: false, followLinks: false, @@ -934,8 +851,8 @@ class ParamsOutTest extends Dsl2Spec { optional: false, includeInputs: false, arity: '1' - - path y, + + path y, maxDepth:5, hidden: true, followLinks: true, @@ -946,9 +863,12 @@ class ParamsOutTest extends Dsl2Spec { includeInputs: true, arity: '0..*' - return '' + script: + x = 'x' + y = 'y' + '' } - + workflow { foo() } ''' @@ -987,9 +907,12 @@ class ParamsOutTest extends Dsl2Spec { output: tuple path(x,maxDepth:1,optional:false), path(y,maxDepth:2,optional:true) - return '' + script: + x = 'x' + y = 'y' + '' } - + workflow { foo() } ''' @@ -1013,15 +936,17 @@ class ParamsOutTest extends Dsl2Spec { setup: def text = ''' - + process hola { output: - val x - tuple val(x), path(x) + val x + tuple val(x), path(x) + script: + x = 'x' /command/ } - + workflow { hola() } ''' when: @@ -1055,9 +980,13 @@ class ParamsOutTest extends Dsl2Spec { val 'str', emit: ch2 val "${y}",emit: ch3 val x.y, emit: ch4 + + script: + x = [:] + y = 'y' /return/ } - + workflow { hola() } ''' @@ -1095,12 +1024,15 @@ class ParamsOutTest extends Dsl2Spec { process hola { output: val x, emit: ch0 - env FOO, emit: ch1 + env 'FOO', emit: ch1 path '-', emit: ch2 - stdout emit: ch3 + stdout emit: ch3 + + script: + x = 'x' /return/ } - + workflow { hola() } ''' @@ -1133,9 +1065,13 @@ class ParamsOutTest extends Dsl2Spec { path x, emit: ch0 path 'file.*', emit: ch1 path "${y}", emit: ch2 + + script: + x = 'x' + y = 'y' /return/ } - + workflow { hola() } ''' @@ -1163,11 +1099,14 @@ class ParamsOutTest extends Dsl2Spec { output: tuple val(x), val(y), emit: ch1 tuple path('foo'), emit: ch2 - tuple stdout,env(bar), emit: ch3 + tuple stdout, env('bar'), emit: ch3 + script: + x = 'x' + y = 'y' /return/ } - + workflow { hola() } ''' @@ -1205,12 +1144,15 @@ class ParamsOutTest extends Dsl2Spec { process hola { output: val x, topic: ch0 - env FOO, topic: ch1 + env 'FOO', topic: ch1 path '-', topic: ch2 - stdout topic: ch3 + stdout topic: ch3 + + script: + x = 'x' /return/ } - + workflow { hola() } ''' @@ -1243,11 +1185,14 @@ class ParamsOutTest extends Dsl2Spec { output: tuple val(x), val(y), topic: ch1 tuple path('foo'), topic: ch2 - tuple stdout,env(bar), topic: ch3 + tuple stdout, env('bar'), topic: ch3 + script: + x = 'x' + y = 'y' /return/ } - + workflow { hola() } ''' diff --git a/modules/nextflow/src/test/groovy/nextflow/script/params/TupleInParamTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/params/TupleInParamTest.groovy index 1b233ad19b..10b4dc8b3f 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/params/TupleInParamTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/params/TupleInParamTest.groovy @@ -16,9 +16,9 @@ package nextflow.script.params -import static test.TestParser.* - import test.Dsl2Spec + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso @@ -28,20 +28,20 @@ class TupleInParamTest extends Dsl2Spec { def 'should create input tuples'() { setup: def text = ''' - x = 'Hola mundo' - process hola { input: - tuple val(p) - tuple val(p), val(q) - tuple val(v), path('file_name.fa') - tuple val(p), path('file_name.txt'), '-' - tuple val(p), path(z, stageAs: 'file*') - + tuple val(r) + tuple val(p), val(q) + tuple val(v), path('file_name.fa') + tuple val(s), path('file_name.txt'), '-' + tuple val(t), path(z, stageAs: 'file*') + + script: /foo/ } - + workflow { + x = 'Hola mundo' hola(x, x, 'str', 'ciao', x) } ''' @@ -59,7 +59,7 @@ class TupleInParamTest extends Dsl2Spec { in1.inner.get(0) instanceof ValueInParam in1.inner.get(0).index == 0 in1.inner.get(0).mapIndex == 0 - in1.inner.get(0).name == 'p' + in1.inner.get(0).name == 'r' in1.inChannel.val == 'Hola mundo' and: in2.inner.size() == 2 @@ -87,7 +87,7 @@ class TupleInParamTest extends Dsl2Spec { and: in4.inner.size() == 3 in4.inner.get(0) instanceof ValueInParam - in4.inner.get(0).name == 'p' + in4.inner.get(0).name == 's' in4.inner.get(0).index == 3 in4.inner.get(0).mapIndex == 0 in4.inner.get(1) instanceof FileInParam @@ -117,20 +117,20 @@ class TupleInParamTest extends Dsl2Spec { setup: def text = ''' - q = 'the file content' - process hola { input: - tuple( file('name_$x') ) - tuple( file("${x}_name.${str}") ) - tuple( file("hola_${x}") ) - tuple file( { "${x}_name.txt" } ) + tuple( val(x), file('name_$x') ) + tuple( file("${x}_name.fastq") ) + tuple( file("hola_${x}") ) + tuple file( { "${x}_name.txt" } ) + script: /foo/ } - + workflow { - hola(q, q, q, q) + q = 'the file content' + hola(['the_file', q], q, q, q) } ''' @@ -140,13 +140,13 @@ class TupleInParamTest extends Dsl2Spec { TupleInParam in1 = process.config.getInputs().get(1) TupleInParam in2 = process.config.getInputs().get(2) TupleInParam in3 = process.config.getInputs().get(3) - def ctx = [x:'the_file', str: 'fastq'] + def ctx = [x:'the_file'] then: - in0.inChannel.val == 'the file content' - in0.inner[0] instanceof FileInParam - (in0.inner[0] as FileInParam).name == 'name_$x' - (in0.inner[0] as FileInParam).getFilePattern(ctx) == 'name_$x' + in0.inChannel.val == ['the_file', 'the file content'] + in0.inner[1] instanceof FileInParam + (in0.inner[1] as FileInParam).name == 'name_$x' + (in0.inner[1] as FileInParam).getFilePattern(ctx) == 'name_$x' in1.inner[0] instanceof FileInParam (in1.inner[0] as FileInParam).name == '__$fileinparam<1:0>' diff --git a/modules/nextflow/src/test/groovy/nextflow/script/params/TupleOutParamTest.groovy b/modules/nextflow/src/test/groovy/nextflow/script/params/TupleOutParamTest.groovy index 65c9981a37..510961ecd0 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/params/TupleOutParamTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/params/TupleOutParamTest.groovy @@ -16,10 +16,10 @@ package nextflow.script.params -import static test.TestParser.* - import groovyx.gpars.dataflow.DataflowVariable import test.Dsl2Spec + +import static test.ScriptHelper.* /** * * @author Paolo Di Tommaso @@ -31,16 +31,20 @@ class TupleOutParamTest extends Dsl2Spec { setup: def text = ''' process hola { + input: + tuple val(x), val(y), val(z) + output: - tuple val(x) - tuple val(y), stdout, file('*.fa') - tuple stdout, val(z) + tuple val(x) + tuple val(y), stdout, file('*.fa') + tuple stdout, val(z) - return '' + script: + '' } workflow { - hola() + hola( ['x', 'y', 'z'] ) } ''' @@ -90,16 +94,20 @@ class TupleOutParamTest extends Dsl2Spec { setup: def text = ''' process hola { + input: + tuple val(x), val(y), val(z) + output: - tuple val(x) - tuple val(y), stdout, file('*.fa') - tuple stdout, val(z) + tuple val(x) + tuple val(y), stdout, file('*.fa') + tuple stdout, val(z) - return '' + script: + '' } workflow { - hola() + hola( ['x', 'y', 'z'] ) } ''' @@ -149,8 +157,9 @@ class TupleOutParamTest extends Dsl2Spec { def text = ''' process hola { output: - tuple env(FOO), env(BAR) + tuple env('FOO'), env('BAR') + script: /echo command/ } @@ -165,7 +174,6 @@ class TupleOutParamTest extends Dsl2Spec { when: def outs = process.config.getOutputs() as List then: - println outs.outChannel outs.size() == 1 and: outs[0].outChannel instanceof DataflowVariable @@ -183,24 +191,27 @@ class TupleOutParamTest extends Dsl2Spec { setup: def text = ''' process hola { + input: + val other + output: - tuple eval('this --one'), eval("$other --two") + tuple eval('this --one'), eval("$other --two") + script: /echo command/ } workflow { - hola() + hola('tool') } ''' - def binding = [other:'tool'] - def process = parseAndReturnProcess(text, binding) + def ctx = [other:'tool'] + def process = parseAndReturnProcess(text) when: def outs = process.config.getOutputs() as List then: - println outs.outChannel outs.size() == 1 and: outs[0].outChannel instanceof DataflowVariable @@ -209,11 +220,11 @@ class TupleOutParamTest extends Dsl2Spec { and: outs[0].inner[0] instanceof CmdEvalParam outs[0].inner[0].getName() =~ /nxf_out_eval_\d+/ - (outs[0].inner[0] as CmdEvalParam).getTarget(binding) == 'this --one' + (outs[0].inner[0] as CmdEvalParam).getTarget(ctx) == 'this --one' and: outs[0].inner[1] instanceof CmdEvalParam outs[0].inner[1].getName() =~ /nxf_out_eval_\d+/ - (outs[0].inner[1] as CmdEvalParam).getTarget(binding) == 'tool --two' + (outs[0].inner[1] as CmdEvalParam).getTarget(ctx) == 'tool --two' } } diff --git a/modules/nextflow/src/testFixtures/groovy/test/LockManagerTest.groovy b/modules/nextflow/src/test/groovy/nextflow/util/LockManagerTest.groovy similarity index 100% rename from modules/nextflow/src/testFixtures/groovy/test/LockManagerTest.groovy rename to modules/nextflow/src/test/groovy/nextflow/util/LockManagerTest.groovy diff --git a/modules/nextflow/src/testFixtures/groovy/test/Dsl2Spec.groovy b/modules/nextflow/src/testFixtures/groovy/test/Dsl2Spec.groovy index 63ef37765b..eea81753ab 100644 --- a/modules/nextflow/src/testFixtures/groovy/test/Dsl2Spec.groovy +++ b/modules/nextflow/src/testFixtures/groovy/test/Dsl2Spec.groovy @@ -16,8 +16,6 @@ package test -import java.nio.file.Path - import groovy.util.logging.Slf4j import nextflow.Global import nextflow.NF @@ -41,18 +39,4 @@ class Dsl2Spec extends BaseSpec { Global.reset() NF.init() } - - def dsl_eval(String str) { - new MockScriptRunner().setScript(str).execute() - } - - def dsl_eval(Path path) { - new MockScriptRunner().setScript(path).execute() - } - - - def dsl_eval(String entry, String str) { - new MockScriptRunner() - .setScript(str).execute(null, null, null, entry) - } } diff --git a/modules/nextflow/src/testFixtures/groovy/test/MockHelpers.groovy b/modules/nextflow/src/testFixtures/groovy/test/MockHelpers.groovy deleted file mode 100644 index d4f5065c0d..0000000000 --- a/modules/nextflow/src/testFixtures/groovy/test/MockHelpers.groovy +++ /dev/null @@ -1,194 +0,0 @@ -package test - -import java.nio.file.Paths - -import groovy.util.logging.Slf4j -import groovyx.gpars.dataflow.DataflowBroadcast -import nextflow.Session -import nextflow.executor.Executor -import nextflow.executor.ExecutorFactory -import nextflow.processor.TaskHandler -import nextflow.processor.TaskMonitor -import nextflow.processor.TaskRun -import nextflow.processor.TaskStatus -import nextflow.script.BaseScript -import nextflow.script.ChannelOut -import nextflow.script.ScriptRunner -import nextflow.script.ScriptType - -@Slf4j -class MockScriptRunner extends ScriptRunner { - - MockScriptRunner() { - super(new MockSession()) - log.debug "Creating MockScriptRunner" - } - - MockScriptRunner(Map config) { - super(new MockSession(config)) - log.debug "Creating MockScriptRunner - config=$config" - } - - MockScriptRunner(MockSession session) { - super(session) - log.debug "Creating MockScriptRunner - session=$session" - } - - MockScriptRunner setScript(String str) { - def script = TestHelper.createInMemTempFile('main.nf', str) - setScript(script) - return this - } - - MockScriptRunner invoke() { - execute() - return this - } - - BaseScript getScript() { getScriptObj() } - - @Override - def normalizeOutput(output) { - if( output instanceof ChannelOut ) { - def list = new ArrayList(output.size()) - for( int i=0; i(output.size()) - for( def item : output ) { - ((List)result).add(read0(item)) - } - return result - } - - else { - return read0(output) - } - } - - - private read0( obj ) { - if( obj instanceof DataflowBroadcast ) - return obj.createReadChannel() - return obj - } - -} - -@Slf4j -class MockSession extends Session { - - @Override - Session start() { - log.debug "MockSession - starting" - this.executorFactory = new MockExecutorFactory() - return super.start() - } - - MockSession() { - super() - log.debug "MockSession - creating" - } - - MockSession(Map config) { - super(config) - log.debug "MockSession - creating with config=$config" - } -} - -class MockExecutorFactory extends ExecutorFactory { - @Override - protected Class getExecutorClass(String executorName) { - return MockExecutor - } - - @Override - protected boolean isTypeSupported(ScriptType type, Object executor) { - true - } -} - -/** - * - * @author Paolo Di Tommaso - */ -class MockExecutor extends Executor { - - @Override - void signal() { } - - protected TaskMonitor createTaskMonitor() { - new MockMonitor() - } - - @Override - TaskHandler createTaskHandler(TaskRun task) { - return new MockTaskHandler(task) - } -} - -class MockMonitor implements TaskMonitor { - - void schedule(TaskHandler handler) { - handler.submit() - } - - /** - * Remove the {@code TaskHandler} instance from the queue of tasks to be processed - * - * @param handler A not null {@code TaskHandler} instance - */ - boolean evict(TaskHandler handler) { } - - /** - * Start the monitoring activity for the queued tasks - * @return The instance itself, useful to chain methods invocation - */ - TaskMonitor start() { } - - /** - * Notify when a task terminates - */ - void signal() { } -} - -@Slf4j -class MockTaskHandler extends TaskHandler { - - protected MockTaskHandler(TaskRun task) { - super(task) - } - - @Override - void submit() { - log.info ">> launching mock task: ${task}" - if( task.type == ScriptType.SCRIPTLET ) { - task.workDir = Paths.get('.').complete() - task.stdout = task.script - task.exitStatus = 0 - } - else { - task.code.call() - } - status = TaskStatus.COMPLETED - task.processor.finalizeTask(this) - } - - @Override - boolean checkIfRunning() { - return false - } - - @Override - boolean checkIfCompleted() { - true - } - - @Override - protected void killTask() { } - -} diff --git a/modules/nextflow/src/testFixtures/groovy/test/ScriptHelper.groovy b/modules/nextflow/src/testFixtures/groovy/test/ScriptHelper.groovy new file mode 100644 index 0000000000..7af9d53224 --- /dev/null +++ b/modules/nextflow/src/testFixtures/groovy/test/ScriptHelper.groovy @@ -0,0 +1,279 @@ +/* + * Copyright 2013-2025, Seqera Labs + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package test + +import java.nio.file.Path + +import groovy.transform.InheritConstructors +import groovyx.gpars.dataflow.DataflowBroadcast +import nextflow.Session +import nextflow.config.ConfigParserFactory +import nextflow.executor.Executor +import nextflow.executor.ExecutorFactory +import nextflow.processor.TaskHandler +import nextflow.processor.TaskMonitor +import nextflow.processor.TaskProcessor +import nextflow.processor.TaskRun +import nextflow.processor.TaskStatus +import nextflow.script.BaseScript +import nextflow.script.BodyDef +import nextflow.script.ChannelOut +import nextflow.script.ProcessConfig +import nextflow.script.ProcessFactory +import nextflow.script.ScriptBinding +import nextflow.script.ScriptFile +import nextflow.script.ScriptLoaderFactory +import nextflow.script.ScriptType + +/** + * + * @author Ben Sherman + */ +class ScriptHelper { + + static Map loadConfig(String text) { + return ConfigParserFactory.create().parse(text) + } + + static BaseScript loadScript(Map opts = [:], String text) { + def session = opts.config ? new MockSession(opts.config as Map) : new MockSession() + session.setBinding(new ScriptBinding()) + + session.init(null) + session.start() + + def loader = ScriptLoaderFactory.create(session) + if( opts.module ) + loader.setModule(true) + loader.parse(text) + loader.runScript() + + return loader.getScript() + } + + static TaskProcessor parseAndReturnProcess(String scriptText, Map binding = null) { + def session = new TestSession() + session.setBinding(binding ? new ScriptBinding(binding) : new ScriptBinding()) + + session.init(null) + // session.start() + + ScriptLoaderFactory.create(session) + .parse(scriptText) + .runScript() + + session.fireDataflowNetwork() + // session.await() + // session.destroy() + + return TaskProcessor.currentProcessor() + } + + @InheritConstructors + private static class TestSession extends Session { + + TestProcessFactory processFactory + + @Override + ProcessFactory newProcessFactory(BaseScript script) { + return processFactory = new TestProcessFactory(script, this) + } + } + + private static class TestProcessFactory extends ProcessFactory { + + BaseScript script + Session session + + TestProcessFactory(BaseScript script, Session session) { + super(script, session) + this.script = script + this.session = session + } + + @Override + TaskProcessor newTaskProcessor(String name, Executor executor, ProcessConfig config, BodyDef taskBody) { + new TestTaskProcessor(name, executor, session, script, config, taskBody) + } + } + + @InheritConstructors + private static class TestTaskProcessor extends TaskProcessor { + @Override + def run () { + // this is needed to mimic the out channels normalisation + // made by the real 'run' method - check the superclass + if( config.getOutputs().size() == 0 ) + config.fakeOutput() + } + } + + static Object runScript(String text, Map config = null) { + def session = config ? new MockSession(config) : new MockSession() + session.setBinding(new ScriptBinding()) + + session.init(null) + session.start() + + def loader = ScriptLoaderFactory.create(session) + loader.parse(text) + loader.runScript() + + def result = normalizeResult(loader.getResult()) + + session.fireDataflowNetwork() + session.await() + session.destroy() + + return result + } + + static Object runScript(Map opts = [:], Path path) { + def session = opts.config ? new MockSession(opts.config) : new MockSession() + session.setBinding(new ScriptBinding()) + + session.init( new ScriptFile(path) ) + session.start() + + def loader = ScriptLoaderFactory.create(session) + loader.parse(path) + loader.runScript() + + def result = normalizeResult(loader.getResult()) + + session.fireDataflowNetwork() + session.await() + session.destroy() + + return result + } + + private static Object normalizeResult(value) { + if( value instanceof ChannelOut ) { + def result = new ArrayList<>(value.size()) + for( def el : value ) + result.add(normalizeResult0(el)) + return result.size() == 1 ? result[0] : result + } + + if( value instanceof Object[] || value instanceof List ) { + def result = new ArrayList<>(value.size()) + for( def el : value ) + result.add(normalizeResult0(el)) + return result + } + + return normalizeResult0(value) + } + + private static Object normalizeResult0(value) { + if( value instanceof ChannelOut ) { + def result = new ArrayList<>(value.size()) + for( def el : value ) + result.add(normalizeResult0(el)) + return result.size() == 1 ? result[0] : result + } + + if( value instanceof DataflowBroadcast ) + return value.createReadChannel() + + return value + } + +} + +class MockSession extends Session { + + MockSession(Map config) { + super(config) + } + + MockSession() { + super() + } + + @Override + Session start() { + this.executorFactory = new MockExecutorFactory() + return super.start() + } +} + +class MockExecutorFactory extends ExecutorFactory { + @Override + protected Class getExecutorClass(String executorName) { + return MockExecutor + } + + @Override + protected boolean isTypeSupported(ScriptType type, Object executor) { + true + } +} + +class MockExecutor extends Executor { + + @Override + void signal() {} + + protected TaskMonitor createTaskMonitor() { + new MockMonitor() + } + + @Override + TaskHandler createTaskHandler(TaskRun task) { + return new MockTaskHandler(task) + } +} + +class MockMonitor implements TaskMonitor { + + @Override void schedule(TaskHandler handler) { handler.submit() } + @Override boolean evict(TaskHandler handler) {} + @Override TaskMonitor start() {} + @Override void signal() {} +} + +class MockTaskHandler extends TaskHandler { + + protected MockTaskHandler(TaskRun task) { + super(task) + } + + @Override + void submit() { + if( task.type == ScriptType.SCRIPTLET ) { + task.workDir = Path.of('.').complete() + task.stdout = task.script + task.exitStatus = 0 + } + else { + task.code.call() + } + status = TaskStatus.COMPLETED + task.processor.finalizeTask(this) + } + + @Override + boolean checkIfRunning() { false } + + @Override + boolean checkIfCompleted() { true } + + @Override + protected void killTask() {} +} diff --git a/modules/nextflow/src/testFixtures/groovy/test/TestParser.groovy b/modules/nextflow/src/testFixtures/groovy/test/TestParser.groovy deleted file mode 100644 index 6a3e067545..0000000000 --- a/modules/nextflow/src/testFixtures/groovy/test/TestParser.groovy +++ /dev/null @@ -1,103 +0,0 @@ -/* - * Copyright 2013-2024, Seqera Labs - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package test - - -import groovy.transform.InheritConstructors -import nextflow.Session -import nextflow.executor.Executor -import nextflow.processor.TaskProcessor -import nextflow.script.BaseScript -import nextflow.script.BodyDef -import nextflow.script.ProcessConfig -import nextflow.script.ProcessFactory -import nextflow.script.ScriptBinding -import nextflow.script.ScriptLoaderFactory -/** - * An helper class to parse nextflow script snippets - * - * @author Paolo Di Tommaso - */ -class TestParser { - - Session session - - TestParser( Map config = null ) { - session = config ? new TestSession(config) : new TestSession() - } - - TestParser( Session session1 ) { - session = session1 - } - - TaskProcessor parseAndGetProcess( String scriptText, Map binding=null ) { - if( binding != null ) { - session.binding = new ScriptBinding(binding) - } - - session.init(null,null) - ScriptLoaderFactory.create(session) .runScript(scriptText) - session.fireDataflowNetwork(false) - return TaskProcessor.currentProcessor() - } - - - static TaskProcessor parseAndReturnProcess( String scriptText, Map map = null ) { - new TestParser().parseAndGetProcess(scriptText, map) - } - - static class TestProcessFactory extends ProcessFactory { - - BaseScript script - Session session - - TestProcessFactory(BaseScript script, Session session) { - super(script,session) - this.script = script - this.session = session - } - - @Override - TaskProcessor newTaskProcessor(String name, Executor executor, ProcessConfig config, BodyDef taskBody ) { - new TestTaskProcessor(name, executor, session, script, config, taskBody) - } - - } - - @InheritConstructors - static class TestTaskProcessor extends TaskProcessor { - @Override - def run () { - // this is needed to mimic the out channels normalisation - // made by the real 'run' method - check the superclass - if ( config.getOutputs().size() == 0 ) { - config.fakeOutput() - } - } - } - - @InheritConstructors - static class TestSession extends Session { - - TestProcessFactory processFactory - - @Override - ProcessFactory newProcessFactory(BaseScript script) { - return processFactory = new TestProcessFactory(script, this) - } - } -} diff --git a/modules/nf-commons/src/test/nextflow/plugin/extension/PluginExtensionMethodsTest.groovy b/modules/nf-commons/src/test/nextflow/plugin/extension/PluginExtensionMethodsTest.groovy index 82cf64ebd6..1e70358d01 100644 --- a/modules/nf-commons/src/test/nextflow/plugin/extension/PluginExtensionMethodsTest.groovy +++ b/modules/nf-commons/src/test/nextflow/plugin/extension/PluginExtensionMethodsTest.groovy @@ -26,10 +26,13 @@ import nextflow.plugin.TestPluginManager import spock.lang.Shared import spock.lang.TempDir import test.Dsl2Spec + +import static test.ScriptHelper.* /** * * @author Jorge Aguilera */ +@spock.lang.Timeout(5) class PluginExtensionMethodsTest extends Dsl2Spec { @TempDir @@ -58,16 +61,16 @@ class PluginExtensionMethodsTest extends Dsl2Spec { def 'should execute custom operator extension/1' () { given: - def SCRIPT_TEXT = ''' + def SCRIPT_TEXT = ''' include { goodbye } from 'plugin/nf-test-plugin-hello' - channel - .of('Bye bye folks') - .goodbye() + workflow { + channel.of('Bye bye folks').goodbye() + } ''' when: - def result = dsl_eval(SCRIPT_TEXT) + def result = runScript(SCRIPT_TEXT) then: result.val == 'Bye bye folks' @@ -77,32 +80,32 @@ class PluginExtensionMethodsTest extends Dsl2Spec { def 'should execute custom operator extension/2' () { given: def SCRIPT_TEXT = ''' - include { reverse; goodbye } from 'plugin/nf-test-plugin-hello' - channel - .of('Bye bye folks') - .goodbye() + workflow { + channel.of('Bye bye folks').goodbye() + } ''' when: - def result = dsl_eval(SCRIPT_TEXT) + def result = runScript(SCRIPT_TEXT) then: result.val == 'Bye bye folks' result.val == Channel.STOP - } def 'should execute custom factory extension/1' () { given: def SCRIPT_TEXT = ''' - include { reverse } from 'plugin/nf-test-plugin-hello' + include { reverse } from 'plugin/nf-test-plugin-hello' - channel.reverse('a string') + workflow { + channel.reverse('a string') + } ''' when: - def result = dsl_eval(SCRIPT_TEXT) + def result = runScript(SCRIPT_TEXT) then: result @@ -114,15 +117,16 @@ class PluginExtensionMethodsTest extends Dsl2Spec { def 'should execute custom factory extension/2' () { given: def SCRIPT_TEXT = ''' - include { reverse } from 'plugin/nf-test-plugin-hello' include { goodbye } from 'plugin/nf-test-plugin-hello' - - channel.reverse('a string') + + workflow { + channel.reverse('a string') + } ''' when: - def result = dsl_eval(SCRIPT_TEXT) + def result = runScript(SCRIPT_TEXT) then: result @@ -136,13 +140,13 @@ class PluginExtensionMethodsTest extends Dsl2Spec { def SCRIPT_TEXT = ''' include { goodbye as myFunction } from 'plugin/nf-test-plugin-hello' - channel - .of(100,200,300) - .myFunction() + workflow { + channel.of(100,200,300).myFunction() + } ''' when: - def result = dsl_eval(SCRIPT_TEXT) + def result = runScript(SCRIPT_TEXT) then: result.val == 100 @@ -154,14 +158,15 @@ class PluginExtensionMethodsTest extends Dsl2Spec { def 'should execute custom factory as alias extension' () { given: def SCRIPT_TEXT = ''' - nextflow.enable.dsl=2 include { reverse as myFunction } from 'plugin/nf-test-plugin-hello' - - channel.myFunction('reverse this string') + + workflow { + channel.myFunction('reverse this string') + } ''' when: - def result = dsl_eval(SCRIPT_TEXT) + def result = runScript(SCRIPT_TEXT) then: result @@ -173,15 +178,15 @@ class PluginExtensionMethodsTest extends Dsl2Spec { def 'should not include operators without the right signature' () { given: def SCRIPT_TEXT = ''' - nextflow.enable.dsl=2 include { goodbyeWrongSignature } from 'plugin/nf-test-plugin-hello' - channel - .of('Bye bye folks') | goodbyeWrongSignature + workflow { + channel.of('Bye bye folks').goodbyeWrongSignature() + } ''' when: - dsl_eval(SCRIPT_TEXT) + runScript(SCRIPT_TEXT) then: thrown(MissingMethodException) @@ -191,14 +196,15 @@ class PluginExtensionMethodsTest extends Dsl2Spec { def 'should not include factories without the right signature' () { given: def SCRIPT_TEXT = ''' - nextflow.enable.dsl=2 - include { reverseCantBeImportedBecauseWrongSignature } from 'plugin/nf-test-plugin-hello' + include { reverseCantBeImportedBecauseWrongSignature } from 'plugin/nf-test-plugin-hello' - channel.reverseCantBeImportedBecauseWrongSignature('a string') + workflow { + channel.reverseCantBeImportedBecauseWrongSignature('a string') + } ''' when: - dsl_eval(SCRIPT_TEXT) + runScript(SCRIPT_TEXT) then: thrown(IllegalStateException) @@ -207,7 +213,7 @@ class PluginExtensionMethodsTest extends Dsl2Spec { def 'should execute custom functions'() { when: - def result = dsl_eval(SCRIPT_TEXT) + def result = runScript(SCRIPT_TEXT) then: result.val == EXPECTED @@ -215,33 +221,38 @@ class PluginExtensionMethodsTest extends Dsl2Spec { where: SCRIPT_TEXT | EXPECTED - "include { sayHello } from 'plugin/nf-test-plugin-hello'; channel.of( sayHello() )" | 'hi' - "include { sayHello } from 'plugin/nf-test-plugin-hello'; channel.of( sayHello('es') )" | 'hola' - "include { sayHello as hi } from 'plugin/nf-test-plugin-hello'; channel.of( hi() )" | 'hi' + "include { sayHello } from 'plugin/nf-test-plugin-hello'; workflow { channel.of( sayHello() ) }" | 'hi' + "include { sayHello } from 'plugin/nf-test-plugin-hello'; workflow { channel.of( sayHello('es') ) }" | 'hola' + "include { sayHello as hi } from 'plugin/nf-test-plugin-hello'; workflow { channel.of( hi() ) }" | 'hi' } def 'should call init plugin in custom functions'() { when: - def result = dsl_eval(""" - include { sayHello } from 'plugin/nf-test-plugin-hello' - sayHello() - """) + def result = runScript(""" + include { sayHello } from 'plugin/nf-test-plugin-hello' + + workflow { + sayHello() + } + """) then: - true + noExceptionThrown() } def 'should throw function not found'() { given: def SCRIPT_TEXT = ''' - include { sayHelloNotExist } from 'plugin/nf-test-plugin-hello' - - channel.of( sayHelloNotExist() ) - ''' + include { sayHelloNotExist } from 'plugin/nf-test-plugin-hello' + + workflow { + channel.of( sayHelloNotExist() ) + } + ''' when: - dsl_eval(SCRIPT_TEXT) + runScript(SCRIPT_TEXT) then: thrown(IllegalStateException) @@ -250,36 +261,37 @@ class PluginExtensionMethodsTest extends Dsl2Spec { def 'should not allow to include an existing function'() { given: def SCRIPT_TEXT = ''' - nextflow.enable.strict = true - - def sayHello(){ 'hi' } - - include { sayHello } from 'plugin/nf-test-plugin-hello' - - channel.of( sayHello() ) - ''' + def sayHello() { 'hi' } + + include { sayHello } from 'plugin/nf-test-plugin-hello' + + workflow { + channel.of( sayHello() ) + } + ''' when: - dsl_eval(SCRIPT_TEXT) + runScript(SCRIPT_TEXT) then: - thrown(DuplicateModuleFunctionException) + def e = thrown(Exception) + e.cause.message.contains('`sayHello` is already included') } def 'should allows to include an existing function but as alias'() { given: def SCRIPT_TEXT= ''' - nextflow.enable.strict = true - - def sayHello(){ 'hi' } - - include { sayHello as anotherHello } from 'plugin/nf-test-plugin-hello' - - channel.of( anotherHello() ) - ''' + def sayHello() { 'hi' } + + include { sayHello as anotherHello } from 'plugin/nf-test-plugin-hello' + + workflow { + channel.of( anotherHello() ) + } + ''' when: - def result = dsl_eval(SCRIPT_TEXT) + def result = runScript(SCRIPT_TEXT) then: result.val == 'hi' @@ -288,16 +300,16 @@ class PluginExtensionMethodsTest extends Dsl2Spec { def 'should not include a non annotated function'() { given: - def SCRIPT_TEXT= ''' - nextflow.enable.strict = true - - include { aNonImportedFunction } from 'plugin/nf-test-plugin-hello' - - channel.of( aNonImportedFunction() ) - ''' + def SCRIPT_TEXT= ''' + include { aNonImportedFunction } from 'plugin/nf-test-plugin-hello' + + workflow { + channel.of( aNonImportedFunction() ) + } + ''' when: - dsl_eval(SCRIPT_TEXT) + runScript(SCRIPT_TEXT) then: thrown(IllegalStateException) @@ -309,32 +321,29 @@ class PluginExtensionMethodsTest extends Dsl2Spec { def MODULE = folder.resolve('module.nf') MODULE.text = ''' - nextflow.enable.strict = true - - include { sayHello } from 'plugin/nf-test-plugin-hello' + include { sayHello } from 'plugin/nf-test-plugin-hello' process foo { input: val lng output: stdout - + + script: "${sayHello(lng)}" } ''' SCRIPT.text = ''' - include { foo } from './module.nf' - workflow{ - main: - foo( 'en' ) - emit: - foo.out + include { foo } from './module.nf' + + workflow { + foo( 'en' ) } ''' when: - def result = dsl_eval(SCRIPT) + def result = runScript(SCRIPT) then: result.val == 'hi' @@ -348,49 +357,44 @@ class PluginExtensionMethodsTest extends Dsl2Spec { def MODULE2 = folder.resolve('module2.nf') MODULE1.text = ''' - nextflow.enable.strict=true - - include { sayHello } from 'plugin/nf-test-plugin-hello' + include { sayHello } from 'plugin/nf-test-plugin-hello' process foo { input: val lng output: stdout - + + script: "${sayHello(lng)}" } ''' MODULE2.text = ''' - nextflow.enable.strict=true - - include { sayHello } from 'plugin/nf-test-plugin-hello' + include { sayHello } from 'plugin/nf-test-plugin-hello' process bar { input: val lng output: stdout - + + script: "${sayHello('es')}" } ''' SCRIPT.text = ''' - include { foo } from './module1.nf' + include { foo } from './module1.nf' include { bar } from './module2.nf' - - workflow{ - main: - foo( 'en' ) | bar - emit: - bar.out + + workflow { + foo( 'en' ) | bar } ''' when: - def result = dsl_eval(SCRIPT) + def result = runScript(SCRIPT) then: result.val == 'hola' @@ -400,19 +404,20 @@ class PluginExtensionMethodsTest extends Dsl2Spec { given: def SCRIPT = folder.resolve('main.nf') - SCRIPT.text = SCRIPT_TEXT + SCRIPT.text = ''' + include { sayHello; goodbye } from 'plugin/nf-test-plugin-hello' + + workflow { + channel.of( sayHello() ).goodbye() + } + ''' when: - def result = dsl_eval(SCRIPT) + def result = runScript(SCRIPT) then: - result.val == EXPECTED + result.val == 'hi' result.val == Channel.STOP - - where: - SCRIPT_TEXT | EXPECTED - "include { sayHello; goodbye } from 'plugin/nf-test-plugin-hello'; channel.of( sayHello() ).goodbye() " | 'hi' - } def 'should not allow a function with the same name as a process'() { @@ -420,31 +425,29 @@ class PluginExtensionMethodsTest extends Dsl2Spec { def SCRIPT = folder.resolve('main.nf') SCRIPT.text = """ - nextflow.enable.strict=true - - include { sayHello } from 'plugin/nf-test-plugin-hello' + include { sayHello } from 'plugin/nf-test-plugin-hello' process sayHello { input: val lng output: stdout - + + script: "Hi" } - workflow{ - main: - Channel.of('hi') | sayHello - emit: - sayHello.out + + workflow { + channel.of('hi') | sayHello } """.stripIndent() when: - dsl_eval(SCRIPT) + runScript(SCRIPT) then: - thrown(DuplicateModuleFunctionException) + def e = thrown(Exception) + e.cause.message.contains('`sayHello` is already included') } def 'should not allow a function and a process with the same from other module'() { @@ -453,45 +456,41 @@ class PluginExtensionMethodsTest extends Dsl2Spec { def MODULE1 = folder.resolve('module1.nf') MODULE1.text = ''' - nextflow.enable.strict=true - process sayHello { input: val lng output: stdout - + + script: "$lng" } ''' SCRIPT.text = """ - nextflow.enable.strict=true - - include { sayHello } from 'plugin/nf-test-plugin-hello' - include { sayHello } from './module1.nf' + include { sayHello } from 'plugin/nf-test-plugin-hello' + include { sayHello } from './module1.nf' process foo { input: val lng output: stdout - + + script: "Hi" } - workflow{ - main: - Channel.of('hi') | foo - emit: - foo.out + + workflow { + channel.of('hi') | foo } """.stripIndent() when: - dsl_eval(SCRIPT) + runScript(SCRIPT) then: - thrown(DuplicateModuleFunctionException) - + def e = thrown(Exception) + e.cause.message.contains('`sayHello` is already included') } } diff --git a/modules/nf-commons/src/test/nextflow/plugin/hello/HelloDslTest.groovy b/modules/nf-commons/src/test/nextflow/plugin/hello/HelloDslTest.groovy index 732be8a9b0..390e6d11c4 100644 --- a/modules/nf-commons/src/test/nextflow/plugin/hello/HelloDslTest.groovy +++ b/modules/nf-commons/src/test/nextflow/plugin/hello/HelloDslTest.groovy @@ -19,17 +19,17 @@ package nextflow.plugin.hello import nextflow.Channel import nextflow.plugin.extension.PluginExtensionProvider -import spock.lang.Specification import spock.lang.Timeout -import test.MockScriptRunner +import test.Dsl2Spec +import static test.ScriptHelper.runScript /** * @author : jorge * */ @Timeout(10) -class HelloDslTest extends Specification{ +class HelloDslTest extends Dsl2Spec { def setup () { def ext = new PluginExtensionProvider(){ @@ -47,7 +47,7 @@ class HelloDslTest extends Specification{ channel.reverse('hi!') ''' and: - def result = new MockScriptRunner([:]).setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == '!ih' result.val == Channel.STOP @@ -56,15 +56,12 @@ class HelloDslTest extends Specification{ def 'should store a goodbye' () { when: def SCRIPT = ''' - channel - .of('Bye bye folks') - .goodbye() + channel.of('Bye bye folks').goodbye() ''' and: - def result = new MockScriptRunner([:]).setScript(SCRIPT).execute() + def result = runScript(SCRIPT) then: result.val == 'Bye bye folks' result.val == Channel.STOP - } } diff --git a/modules/nf-lang/src/main/java/nextflow/config/control/StripSecretsVisitor.java b/modules/nf-lang/src/main/java/nextflow/config/control/StripSecretsVisitor.java index 1d45c7f218..bb2a15dedd 100644 --- a/modules/nf-lang/src/main/java/nextflow/config/control/StripSecretsVisitor.java +++ b/modules/nf-lang/src/main/java/nextflow/config/control/StripSecretsVisitor.java @@ -50,14 +50,18 @@ protected SourceUnit getSourceUnit() { @Override public Expression transform(Expression node) { - if( node instanceof ClosureExpression ce ) - super.visitClosureExpression(ce); + if( node instanceof ClosureExpression ce ) { + ce.visit(this); + return ce; + } - if( node instanceof MethodCallExpression mce ) + if( node instanceof MethodCallExpression mce ) { return transformMethodCall(mce); + } - if( node instanceof PropertyExpression pe ) + if( node instanceof PropertyExpression pe ) { return transformProperty(pe); + } return super.transform(node); } diff --git a/modules/nf-lang/src/main/java/nextflow/script/control/GStringToStringVisitor.java b/modules/nf-lang/src/main/java/nextflow/script/control/GStringToStringVisitor.java index 38d8a2dc1a..8a359e0d9c 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/control/GStringToStringVisitor.java +++ b/modules/nf-lang/src/main/java/nextflow/script/control/GStringToStringVisitor.java @@ -53,11 +53,14 @@ protected SourceUnit getSourceUnit() { @Override public Expression transform(Expression node) { - if( node instanceof ClosureExpression ce ) - super.visitClosureExpression(ce); + if( node instanceof ClosureExpression ce ) { + ce.visit(this); + return ce; + } - if( node instanceof GStringExpression gse ) + if( node instanceof GStringExpression gse ) { return transformToString(gse); + } return super.transform(node); } diff --git a/modules/nf-lang/src/main/java/nextflow/script/control/OpCriteriaVisitor.java b/modules/nf-lang/src/main/java/nextflow/script/control/OpCriteriaVisitor.java index a6c055be71..148fa8f59f 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/control/OpCriteriaVisitor.java +++ b/modules/nf-lang/src/main/java/nextflow/script/control/OpCriteriaVisitor.java @@ -69,11 +69,14 @@ protected SourceUnit getSourceUnit() { @Override public Expression transform(Expression node) { - if( node instanceof ClosureExpression ce ) - super.visitClosureExpression(ce); + if( node instanceof ClosureExpression ce ) { + ce.visit(this); + return ce; + } - if( node instanceof MethodCallExpression mce ) + if( node instanceof MethodCallExpression mce ) { return transformMethodCall(mce); + } return super.transform(node); } diff --git a/modules/nf-lang/src/main/java/nextflow/script/control/PathCompareVisitor.java b/modules/nf-lang/src/main/java/nextflow/script/control/PathCompareVisitor.java index 59f8a8a026..a5b1caf69a 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/control/PathCompareVisitor.java +++ b/modules/nf-lang/src/main/java/nextflow/script/control/PathCompareVisitor.java @@ -52,11 +52,14 @@ protected SourceUnit getSourceUnit() { @Override public Expression transform(Expression node) { - if( node instanceof BinaryExpression be ) + if( node instanceof BinaryExpression be ) { return transformBinaryExpression(be); + } - if( node instanceof ClosureExpression ce ) - super.visitClosureExpression(ce); + if( node instanceof ClosureExpression ce ) { + ce.visit(this); + return ce; + } return super.transform(node); } diff --git a/modules/nf-lang/src/main/java/nextflow/script/control/ScriptToGroovyVisitor.java b/modules/nf-lang/src/main/java/nextflow/script/control/ScriptToGroovyVisitor.java index dbfb610181..35b3553177 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/control/ScriptToGroovyVisitor.java +++ b/modules/nf-lang/src/main/java/nextflow/script/control/ScriptToGroovyVisitor.java @@ -34,9 +34,11 @@ import nextflow.script.ast.WorkflowNode; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.CodeVisitorSupport; +import org.codehaus.groovy.ast.MethodNode; import org.codehaus.groovy.ast.Parameter; import org.codehaus.groovy.ast.VariableScope; import org.codehaus.groovy.ast.expr.BinaryExpression; +import org.codehaus.groovy.ast.expr.DeclarationExpression; import org.codehaus.groovy.ast.expr.Expression; import org.codehaus.groovy.ast.expr.MethodCallExpression; import org.codehaus.groovy.ast.expr.VariableExpression; @@ -141,6 +143,9 @@ public void visitParamV1(ParamNodeV1 node) { @Override public void visitWorkflow(WorkflowNode node) { + if( !node.isEntry() ) + checkReservedMethodName(node, "workflow"); + var main = node.main instanceof BlockStatement block ? block : new BlockStatement(); visitWorkflowEmits(node.emits, main); visitWorkflowPublishers(node.publishers, main); @@ -215,25 +220,29 @@ private void visitWorkflowHandler(Statement code, String name, BlockStatement ma @Override public void visitProcessV2(ProcessNodeV2 node) { + checkReservedMethodName(node, "process"); var result = new ProcessToGroovyVisitorV2(sourceUnit).transform(node); moduleNode.addStatement(result); } @Override public void visitProcessV1(ProcessNodeV1 node) { + checkReservedMethodName(node, "process"); var result = new ProcessToGroovyVisitorV1(sourceUnit).transform(node); moduleNode.addStatement(result); } @Override public void visitFunction(FunctionNode node) { - if( RESERVED_NAMES.contains(node.getName()) ) { - syntaxError(node, "`" + node.getName() + "` is not allowed as a function name because it is reserved for internal use"); - return; - } + checkReservedMethodName(node, "function"); moduleNode.getScriptClassDummy().addMethod(node); } + private void checkReservedMethodName(MethodNode node, String typeLabel) { + if( RESERVED_NAMES.contains(node.getName()) ) + syntaxError(node, "`" + node.getName() + "` is not allowed as a " + typeLabel + " name because it is reserved for internal use"); + } + @Override public void visitOutputs(OutputBlockNode node) { var statements = node.declarations.stream() diff --git a/modules/nf-lang/src/main/java/nextflow/script/control/StripTypesVisitor.java b/modules/nf-lang/src/main/java/nextflow/script/control/StripTypesVisitor.java index ed8711478f..7dc7fe3b0c 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/control/StripTypesVisitor.java +++ b/modules/nf-lang/src/main/java/nextflow/script/control/StripTypesVisitor.java @@ -63,14 +63,18 @@ protected SourceUnit getSourceUnit() { @Override public Expression transform(Expression node) { - if( node instanceof CastExpression ce ) + if( node instanceof CastExpression ce ) { return stripTypeAnnotation(ce); + } - if( node instanceof ClosureExpression ce ) - super.visitClosureExpression(ce); + if( node instanceof ClosureExpression ce ) { + ce.visit(this); + return ce; + } - if( node instanceof DeclarationExpression de ) + if( node instanceof DeclarationExpression de ) { stripTypeAnnotation(de); + } return super.transform(node); } diff --git a/modules/nf-lang/src/main/java/nextflow/script/control/VariableScopeVisitor.java b/modules/nf-lang/src/main/java/nextflow/script/control/VariableScopeVisitor.java index 4d3fd88a70..b8e0829d3b 100644 --- a/modules/nf-lang/src/main/java/nextflow/script/control/VariableScopeVisitor.java +++ b/modules/nf-lang/src/main/java/nextflow/script/control/VariableScopeVisitor.java @@ -160,7 +160,7 @@ private void declareMethod(MethodNode mn) { vsc.addError("`" + name + "` is already included", mn, "First included here", otherInclude); } var otherMethods = cn.getDeclaredMethods(name); - if( otherMethods.size() > 0 ) { + if( !(mn instanceof FunctionNode) && otherMethods.size() > 0 ) { var other = otherMethods.get(0); var first = mn.getLineNumber() < other.getLineNumber() ? mn : other; var second = mn.getLineNumber() < other.getLineNumber() ? other : mn;