diff --git a/docs/reference/feature-flags.md b/docs/reference/feature-flags.md index 640c1e9fb9..9f687b60fb 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 ac1e7fd5c9..6e5cdc0220 100644 --- a/docs/strict-syntax.md +++ b/docs/strict-syntax.md @@ -16,6 +16,10 @@ export NXF_SYNTAX_PARSER=v2 ``` ::: +:::{versionchanged} 26.04.0 +The strict syntax is enabled by default. You can disable it 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/cli/CmdRun.groovy b/modules/nextflow/src/main/groovy/nextflow/cli/CmdRun.groovy index 309ef27d3f..8d26c2a976 100644 --- a/modules/nextflow/src/main/groovy/nextflow/cli/CmdRun.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/cli/CmdRun.groovy @@ -492,8 +492,9 @@ class CmdRun extends CmdBase implements HubOptions { } static void detectStrictFeature(ConfigMap config, Map sysEnv) { + if( NF.isSyntaxParserV2() ) + return final defStrict = sysEnv.get('NXF_ENABLE_STRICT') ?: false - log final strictMode = config.navigate('nextflow.enable.strict', defStrict) if( strictMode ) { log.debug "Enabling nextflow strict mode" @@ -522,8 +523,10 @@ class CmdRun extends CmdBase implements HubOptions { } static String detectDslMode(ConfigMap config, String scriptText, Map sysEnv) { - // -- try determine DSL version from config file + if( NF.isSyntaxParserV2() ) + return DSL2 + // -- try determine DSL version from config file final dsl = config.navigate('nextflow.enable.dsl') as String // -- script can still override the DSL version diff --git a/modules/nextflow/src/main/groovy/nextflow/config/ConfigBuilder.groovy b/modules/nextflow/src/main/groovy/nextflow/config/ConfigBuilder.groovy index 1bb80cf0ea..9fe1752429 100644 --- a/modules/nextflow/src/main/groovy/nextflow/config/ConfigBuilder.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/config/ConfigBuilder.groovy @@ -85,7 +85,7 @@ class ConfigBuilder { List warnings = new ArrayList<>(10) - Map declaredParams = [:] + Map declaredParams ConfigBuilder() { setHomeDir(Const.APP_HOME_DIR) @@ -407,6 +407,7 @@ class ConfigBuilder { checkValidProfile(parser.getDeclaredProfiles()) } + this.declaredParams = parser.getDeclaredParams() } // guarantee top scopes @@ -438,7 +439,6 @@ class ConfigBuilder { final config = parse0(parser, entry) if( NF.getSyntaxParserVersion() == 'v1' ) validate(config, entry) - declaredParams.putAll(parser.getDeclaredParams()) result.merge(config) } 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..54b6f4e3ee 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 @@ -19,6 +19,7 @@ package nextflow.config.parser.v2 import java.nio.file.NoSuchFileException import java.nio.file.Path +import groovy.transform.CompileDynamic import groovy.transform.CompileStatic import groovy.transform.Memoized import groovy.util.logging.Slf4j @@ -42,13 +43,15 @@ class ConfigDsl extends Script { private boolean strict + private boolean stripSecrets + private Path configPath - private Map paramOverrides + private Map cliParams private List profiles - private Map target = [:] + private Map target = [params: [:]] private Set declaredProfiles = [] @@ -66,13 +69,21 @@ class ConfigDsl extends Script { this.strict = value } + void setStripSecrets(boolean value) { + this.stripSecrets = value + } + void setConfigPath(Path path) { this.configPath = path } - void setParams(Map paramOverrides) { - this.paramOverrides = paramOverrides - target.params = paramOverrides + void setParams(Map params) { + this.cliParams = params + (target.params as Map).putAll(params) + } + + void setConfigParams(Map params) { + (target.params as Map).putAll(params) } void setProfiles(List profiles) { @@ -120,7 +131,7 @@ class ConfigDsl extends Script { void assign(List names, Object value) { if( names.size() == 2 && names.first() == 'params' ) { declareParam(names.last(), value) - if( paramOverrides.containsKey(names.last()) ) + if( cliParams.containsKey(names.last()) ) return } navigate(names.init()).put(names.last(), value) @@ -195,8 +206,10 @@ class ConfigDsl extends Script { .setIgnoreIncludes(ignoreIncludes) .setRenderClosureAsString(renderClosureAsString) .setStrict(strict) + .setStripSecrets(stripSecrets) .setBinding(binding.getVariables()) - .setParams(target.params as Map) + .setParams(cliParams) + .setConfigParams(target.params as Map) .setProfiles(profiles) final config = parser.parse(configText, includePath) declaredProfiles.addAll(parser.getDeclaredProfiles()) @@ -213,6 +226,7 @@ class ConfigDsl extends Script { * @param includePath */ @Memoized + @CompileDynamic // required to support ProviderPath::getText() over NioExtensions::getText() protected static String readConfigFile(Path includePath) { try { return includePath.getText() 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..d76c57484d 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 @@ -18,6 +18,7 @@ package nextflow.config.parser.v2 import java.nio.file.Path +import groovy.transform.CompileDynamic import groovy.transform.CompileStatic import nextflow.config.ConfigParser import nextflow.exception.ConfigParseException @@ -36,7 +37,9 @@ class ConfigParserV2 implements ConfigParser { private Map bindingVars = [:] - private Map paramOverrides = [:] + private Map cliParams = [:] + + private Map configParams = [:] private boolean ignoreIncludes = false @@ -48,9 +51,9 @@ class ConfigParserV2 implements ConfigParser { private List appliedProfiles - private Set declaredProfiles + private Set declaredProfiles = [] - private Map declaredParams + private Map declaredParams = [:] private GroovyShell groovyShell @@ -79,7 +82,7 @@ class ConfigParserV2 implements ConfigParser { } @Override - ConfigParser setStripSecrets(boolean value) { + ConfigParserV2 setStripSecrets(boolean value) { this.stripSecrets = value return this } @@ -91,10 +94,15 @@ class ConfigParserV2 implements ConfigParser { } @Override - ConfigParserV2 setParams(Map vars) { - // deep clone the map to prevent side-effect + ConfigParserV2 setParams(Map params) { + // deep clone the map to prevent side effects with nested params // see https://github.com/nextflow-io/nextflow/issues/1923 - this.paramOverrides = Bolts.deepClone(vars) + this.cliParams = Bolts.deepClone(params) + return this + } + + ConfigParserV2 setConfigParams(Map params) { + this.configParams = params return this } @@ -127,14 +135,17 @@ class ConfigParserV2 implements ConfigParser { if( path ) script.setConfigPath(path) script.setIgnoreIncludes(ignoreIncludes) - script.setParams(paramOverrides) - script.setProfiles(appliedProfiles) script.setRenderClosureAsString(renderClosureAsString) + script.setStrict(strict) + script.setStripSecrets(stripSecrets) + script.setParams(cliParams) + script.setConfigParams(configParams) + script.setProfiles(appliedProfiles) script.run() final target = script.getTarget() - declaredProfiles = script.getDeclaredProfiles() - declaredParams = script.getDeclaredParams() + declaredProfiles.addAll(script.getDeclaredProfiles()) + declaredParams.putAll(script.getDeclaredParams()) return Bolts.toConfigObject(target) } catch( CompilationFailedException e ) { @@ -168,8 +179,9 @@ class ConfigParserV2 implements ConfigParser { } @Override + @CompileDynamic // required to support ProviderPath::getText() over NioExtensions::getText() ConfigObject parse(Path path) { - return parse(path.text, path) + return parse(path.getText(), path) } private ConfigCompiler compiler diff --git a/modules/nextflow/src/main/groovy/nextflow/scm/ProviderPath.groovy b/modules/nextflow/src/main/groovy/nextflow/scm/ProviderPath.groovy index 366f57e0db..694032575e 100644 --- a/modules/nextflow/src/main/groovy/nextflow/scm/ProviderPath.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/scm/ProviderPath.groovy @@ -20,6 +20,7 @@ import java.nio.file.LinkOption import java.nio.file.Path import java.nio.file.Paths +import groovy.transform.CompileStatic import groovy.transform.EqualsAndHashCode import groovy.transform.Memoized import groovy.transform.PackageScope @@ -39,6 +40,7 @@ import groovy.util.logging.Slf4j */ @EqualsAndHashCode @Slf4j +@CompileStatic class ProviderPath implements Path { @PackageScope @@ -59,18 +61,27 @@ class ProviderPath implements Path { this.delegate = path } + @Override + Path relativize(Path other) { + new ProviderPath(provider, delegate.relativize(other)) + } + + @Override Path resolveSibling(Path other) { new ProviderPath(provider, delegate.resolveSibling(other.toString())) } + @Override Path resolveSibling(String other) { new ProviderPath(provider, delegate.resolveSibling(other)) } + @Override Path resolve(Path other) { new ProviderPath(provider, delegate.resolve(other.toString())) } + @Override Path resolve(String other) { new ProviderPath(provider, delegate.resolve(other)) } @@ -97,10 +108,16 @@ class ProviderPath implements Path { provider.readText(delegate.toString()) } + @Override + URI toUri() { + URI.create(toUriString()) + } + String toUriString() { provider.getContentUrl(delegate.toString()) } + @Override String toString() { final base = provider.getRepositoryUrl() final file = delegate.toString() @@ -110,6 +127,7 @@ class ProviderPath implements Path { /** * @return the path itself because it's absolute by definition being a remote file */ + @Override Path toAbsolutePath() { return this } @@ -117,6 +135,7 @@ class ProviderPath implements Path { /** * @return {@code true} because it's absolute by definition being a remote file */ + @Override boolean isAbsolute() { return true } @@ -124,6 +143,7 @@ class ProviderPath implements Path { /** * @return the path itself because it's absolute by definition being a remote file */ + @Override Path normalize() { return this } 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..f91c7c9cfb 100644 --- a/modules/nextflow/src/main/groovy/nextflow/script/WorkflowDef.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/script/WorkflowDef.groovy @@ -106,8 +106,6 @@ class WorkflowDef extends BindableDef implements ChainableDef, IterableDef, Exec @PackageScope Map getDeclaredPublish() { declaredPublish } - @PackageScope String getSource() { body.source } - @PackageScope List getDeclaredVariables() { new ArrayList(variableNames) } String getType() { 'workflow' } @@ -197,15 +195,22 @@ class WorkflowDef extends BindableDef implements ChainableDef, IterableDef, Exec } private Object run0(Object[] args) { + // add inputs to workflow binding collectInputs(binding, args) - // invoke the workflow execution + // execute the workflow 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 the last statement if entry workflow (used for testing) + return result + } + else { + // otherwise collect the outputs from the workflow binding + output = collectOutputs(declaredOutputs) + return output + } } } diff --git a/modules/nextflow/src/main/groovy/nextflow/script/parser/v1/ScriptLoaderV1.groovy b/modules/nextflow/src/main/groovy/nextflow/script/parser/v1/ScriptLoaderV1.groovy index 91dc4ad5e9..21876049ea 100644 --- a/modules/nextflow/src/main/groovy/nextflow/script/parser/v1/ScriptLoaderV1.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/script/parser/v1/ScriptLoaderV1.groovy @@ -147,7 +147,7 @@ class ScriptLoaderV1 implements ScriptLoader { * with the implicit and user variables */ protected String computeClassName(script) { - final PREFIX = 'Script_' + final PREFIX = '_nf_script_' if( script instanceof Path ) { return FileHelper.getIdentifier(script,PREFIX) 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..03942a6434 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,16 @@ 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() + // capture the last statement of the snippet or entry workflow (used for testing) + this.result = mainScript.run() return this } @@ -104,17 +110,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/main/groovy/nextflow/util/LoggerHelper.groovy b/modules/nextflow/src/main/groovy/nextflow/util/LoggerHelper.groovy index 18ac88ace0..d7afbe3113 100644 --- a/modules/nextflow/src/main/groovy/nextflow/util/LoggerHelper.groovy +++ b/modules/nextflow/src/main/groovy/nextflow/util/LoggerHelper.groovy @@ -633,17 +633,17 @@ class LoggerHelper { } static List findErrorLine( Throwable e, Map allNames ) { - def lines = getErrorLines(e) - List error = null - for( String str : lines ) { - if( (error=getErrorLine(str,allNames))) { - break - } + final lines = getErrorLines(e) + for( final line : lines ) { + final error = getErrorLine(line, allNames) + if( error ) + return error } - return error + return null } - static @PackageScope String[] getErrorLines(Throwable e) { + @PackageScope + static String[] getErrorLines(Throwable e) { try { return ExceptionUtils.getStackTrace(e).split('\n') } @@ -653,7 +653,7 @@ class LoggerHelper { } } - static private Pattern ERR_LINE_REGEX = ~/\((Script_[0-9a-f]{16}):(\d*)\)$/ + static private Pattern ERR_LINE_REGEX = ~/\((Main|_nf_script_[0-9a-f]{16}):(\d*)\)$/ @PackageScope static List getErrorLine( String str, Map allNames ) { diff --git a/modules/nextflow/src/test/groovy/FunctionalTests.groovy b/modules/nextflow/src/test/groovy/FunctionalTests.groovy index 14318032cd..1a9958a02b 100644 --- a/modules/nextflow/src/test/groovy/FunctionalTests.groovy +++ b/modules/nextflow/src/test/groovy/FunctionalTests.groovy @@ -14,13 +14,14 @@ * limitations under the License. */ -import nextflow.config.ConfigParserFactory -import nextflow.exception.AbortRunException +import java.nio.file.Files + 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 +29,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 +41,7 @@ class FunctionalTests extends Dsl2Spec { maxForks = 10 environment = [a:1, b:2,c:3] } - ''' + ''') and: def script = ''' @@ -73,6 +49,8 @@ class FunctionalTests extends Dsl2Spec { process taskHello { debug true maxForks 11 + + script: 'echo hello' } @@ -80,9 +58,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 +75,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 +92,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 +103,7 @@ class FunctionalTests extends Dsl2Spec { def 'test merge ext properties' () { given: - def config = ''' + def config = loadConfig(''' process { ext { alpha = "aaa" @@ -142,7 +116,7 @@ class FunctionalTests extends Dsl2Spec { } } } - ''' + ''') and: def script = ''' @@ -155,9 +129,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 +142,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 +168,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 +187,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 +238,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 +270,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 +291,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 +318,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 +364,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 +386,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 +415,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 +447,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 +471,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 +496,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 +523,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 +552,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 +584,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 +608,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,56 +638,93 @@ 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() } - def 'should show the line of the error when throw an exception'() { + def 'should show the source line when reporting an error'() { + + given: + def script = Files.createTempFile('test', '.nf') + script.text = '''\ + def thisMethodExpectsOnlyOneString(a: String) { + a + } + + process foo { + input: + val x + + script: + "${thisMethodExpectsOnlyOneString(1)}" + } + + workflow { + foo(1) + } + ''' + + when: + def config = [process:[executor: 'nope']] + runScript(script, config: config) + def processor = TaskProcessor.currentProcessor() + then: + processor.session.fault.report ==~ /(?s).*-- Check script '(.*?)' at line: 10.*/ + } + + def 'should show the source line when reporting an error in a module'() { given: - def script = '''/*1*/ -/*2*/ def thisMethodExpectsOnlyOneString(String a){ -/*3*/ a -/*4*/ } -/*5*/ -/*6*/ process foo { -/*7*/ input: -/*8*/ each x -/*9*/ script: -/*10*/ "${thisMethodExpectsOnlyOneString(1)}" -/*11*/ } -/*12*/ -/*13*/ workflow { foo(1) } - ''' + def folder = Files.createTempDirectory('test') + + def module = folder.resolve('module.nf') + module.text = '''\ + def thisMethodExpectsOnlyOneString(a: String) { + a + } + + process foo { + input: + val x + + script: + "${thisMethodExpectsOnlyOneString(1)}" + } + ''' + + def script = folder.resolve('main.nf') + script.text = '''\ + include { foo } from './module.nf' + + workflow { + foo(1) + } + ''' when: def config = [process:[executor: 'nope']] - def runner = new MockScriptRunner(config) - runner.setScript(script).execute() + runScript(script, config: 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..4a2b09d661 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') } @@ -328,6 +333,8 @@ class CmdRunTest extends Specification { def 'should determine dsl mode' () { given: + SysEnv.push(NXF_SYNTAX_PARSER: 'v1') + def DSL1_SCRIPT = ''' process foo { input: @@ -387,6 +394,9 @@ class CmdRunTest extends Specification { and: // detect version from global default CmdRun.detectDslMode(new ConfigMap(), DSL2_SCRIPT, [:]) == '2' + + cleanup: + SysEnv.pop() } @Unroll @@ -414,6 +424,7 @@ class CmdRunTest extends Specification { @Unroll def 'should detect strict mode' () { given: + SysEnv.push(NXF_SYNTAX_PARSER: 'v1') NextflowMeta.instance.strictMode(INITIAL) CmdRun.detectStrictFeature(new ConfigMap(CONFIG), ENV) @@ -422,6 +433,7 @@ class CmdRunTest extends Specification { cleanup: NextflowMeta.instance.strictMode(false) + SysEnv.pop() where: INITIAL | CONFIG | ENV | EXPECTED diff --git a/modules/nextflow/src/test/groovy/nextflow/config/ConfigBuilderTest.groovy b/modules/nextflow/src/test/groovy/nextflow/config/ConfigBuilderTest.groovy index 0220ea7e2c..98a221c456 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' @@ -375,7 +408,7 @@ class ConfigBuilderTest extends Specification { folder?.deleteDir() } - def 'CLI params should overrides the ones in one or more profiles' () { + def 'CLI params should override params in profiles' () { setup: def file = Files.createTempFile('test',null) @@ -388,28 +421,28 @@ class ConfigBuilderTest extends Specification { params { genomes { 'GRCh37' { - bed12 = '/data/genes.bed' - bismark = '/data/BismarkIndex' - bowtie = '/data/genome' + bed12 = '/data/genes.bed' + bismark = '/data/BismarkIndex' + bowtie = '/data/genome' } } } profiles { - first { - params.alpha = 'Alpha' - params.omega = 'Omega' - params.gamma = 'First' - process.name = 'Bar' - } + first { + params.alpha = 'Alpha' + params.omega = 'Omega' + params.gamma = 'First' + process.name = 'Bar' + } - second { - params.alpha = 'xxx' - params.gamma = 'Second' - process { - publishDir = [path: params.alpha] + second { + params.alpha = 'xxx' + params.gamma = 'Second' + process { + publishDir = [path: params.alpha] + } } - } } ''' @@ -436,16 +469,6 @@ class ConfigBuilderTest extends Specification { config.params.genomes.'GRCh37'.bismark == '/data/BismarkIndex' config.params.genomes.'GRCh37'.bowtie == '/data/genome' - when: - config = configWithParams(file, [params: [alpha: 'AAA', beta: 'BBB', genomes: 'xxx'], profile: 'second']) - then: - config.params.alpha == 'AAA' - config.params.beta == 'BBB' - config.params.delta == 'Foo' - config.params.gamma == 'Second' - config.params.genomes == 'xxx' - config.process.publishDir == [path: 'AAA'] - cleanup: file?.delete() } @@ -1696,87 +1719,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: @@ -2025,7 +1967,7 @@ class ConfigBuilderTest extends Specification { given: def folder = Files.createTempDirectory('test') - def file1 = folder.resolve('test.conf') + def file1 = folder.resolve('test.config') file1.text = ''' process { cpus = 2 @@ -2068,7 +2010,7 @@ class ConfigBuilderTest extends Specification { given: def folder = Files.createTempDirectory('test') - def file1 = folder.resolve('test.conf') + def file1 = folder.resolve('test.config') file1.text = ''' process { ext { args = "Hello World!" } @@ -2099,7 +2041,7 @@ class ConfigBuilderTest extends Specification { given: def folder = Files.createTempDirectory('test') - def file1 = folder.resolve('test.conf') + def file1 = folder.resolve('test.config') file1.text = ''' process { ext.args = "Hello World!" @@ -2126,7 +2068,7 @@ class ConfigBuilderTest extends Specification { def 'should access top params from profile' () { given: def folder = Files.createTempDirectory('test') - def file1 = folder.resolve('file1.conf') + def file1 = folder.resolve('file1.config') file1.text = """ params.alpha = 1 @@ -2157,7 +2099,7 @@ class ConfigBuilderTest extends Specification { def 'should access top params from profile [2]' () { given: def folder = Files.createTempDirectory('test') - def file1 = folder.resolve('file1.conf') + def file1 = folder.resolve('file1.config') file1.text = """ params { @@ -2193,7 +2135,7 @@ class ConfigBuilderTest extends Specification { def 'should merge params two profiles' () { given: def folder = Files.createTempDirectory('test') - def file1 = folder.resolve('file1.conf') + def file1 = folder.resolve('file1.config') file1.text = ''' profiles { @@ -2317,7 +2259,7 @@ class ConfigBuilderTest extends Specification { [foo:1, bar:[x:1, y:2]] | [foo: 2, bar: [x:10, y:20]] | [foo: 2, bar: [x:10, y:20]] } - def 'prevent config side effects' () { + def 'prevent side effects with with nested params' () { given: def folder = Files.createTempDirectory('test') and: @@ -2456,9 +2398,9 @@ class ConfigBuilderTest extends Specification { def 'should merge profiles with conditions' () { given: def folder = Files.createTempDirectory("mergeprofiles") - def main = folder.resolve('main.conf') - def test = folder.resolve('test.conf') - def process = folder.resolve('process.conf') + def main = folder.resolve('main.config') + def test = folder.resolve('test.config') + def process = folder.resolve('process.config') main.text = ''' params { @@ -2467,13 +2409,12 @@ class ConfigBuilderTest extends Specification { } profiles { - test { includeConfig 'test.conf' } + test { includeConfig 'test.config' } } - if (params.load_config) { - includeConfig 'process.conf' - } + includeConfig (params.load_config ? 'process.config' : '/dev/null') ''' + test.text = ''' params { load_config = true 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/scm/ProviderPathTest.groovy b/modules/nextflow/src/test/groovy/nextflow/scm/ProviderPathTest.groovy index 4a6132a288..b3932f72a5 100644 --- a/modules/nextflow/src/test/groovy/nextflow/scm/ProviderPathTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/scm/ProviderPathTest.groovy @@ -28,7 +28,9 @@ class ProviderPathTest extends Specification { def 'should resolve with a github remote provider path' () { given: - def provider = Mock(RepositoryProvider) + def provider = Mock(RepositoryProvider) { + getContentUrl(_) >> { args -> "https://github.com/nf-core/sarek/${args[0]}" } + } def path = new ProviderPath(provider, 'nextflow.config') def MAIN_CONFIG = ''' 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..b272e27122 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,21 @@ 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 - main: - 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 - 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,35 +73,28 @@ 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' + meta.getWorkflow(null) } 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 +105,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 +129,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() @@ -265,56 +200,4 @@ class WorkflowDefTest extends Dsl2Spec { copy.getName() == 'bar' } - - 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 - } - ''' - - 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') - then: - workflow.getSource().stripIndent(true) == '''\ - take: - foo - main: - 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 { } - ''' - - when: - def binding = new ScriptBinding().setSession(Mock(Session)) - def script = (TestScript)new GroovyShell(binding,config).parse(SCRIPT).run() - 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/test/groovy/nextflow/script/parser/v1/ScriptLoaderV1Test.groovy b/modules/nextflow/src/test/groovy/nextflow/script/parser/v1/ScriptLoaderV1Test.groovy index 2a26c2fc4f..034f521bce 100644 --- a/modules/nextflow/src/test/groovy/nextflow/script/parser/v1/ScriptLoaderV1Test.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/script/parser/v1/ScriptLoaderV1Test.groovy @@ -93,10 +93,10 @@ class ScriptLoaderV1Test extends Specification { where: SCRIPT | EXPECTED - 'foo.nf' | 'Script_foo' - 'foo-bar-baz.nf' | 'Script_foo_bar_baz' - '123-fo0' | 'Script_23_fo0' - '--a b c' | 'Script_a_b_c' + 'foo.nf' | '_nf_script_foo' + 'foo-bar-baz.nf' | '_nf_script_foo_bar_baz' + '123-fo0' | '_nf_script_23_fo0' + '--a b c' | '_nf_script_a_b_c' } def 'should normalise script text' () { @@ -107,7 +107,7 @@ class ScriptLoaderV1Test extends Specification { when: def result = parser.computeClassName('process foo { etc } ') then: - result == 'Script_dd540db41b3a8b2a' + result == '_nf_script_dd540db41b3a8b2a' } def 'should set classpath' () { 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/test/groovy/nextflow/util/LoggerHelperTest.groovy b/modules/nextflow/src/test/groovy/nextflow/util/LoggerHelperTest.groovy index 0ad318f117..bd3b51614f 100644 --- a/modules/nextflow/src/test/groovy/nextflow/util/LoggerHelperTest.groovy +++ b/modules/nextflow/src/test/groovy/nextflow/util/LoggerHelperTest.groovy @@ -41,9 +41,9 @@ class LoggerHelperTest extends Specification { given: final pwd = System.getProperty("user.dir") Map names = [ - 'Script_f1bbc0eff1bbc0ef': Paths.get('/some/path/main.nf'), - 'Script_1b751fe91b751fe9': Paths.get('/other/path/module.nf'), - 'Script_1234567812345678': Paths.get("$pwd/foo/script.nf") + '_nf_script_f1bbc0eff1bbc0ef': Paths.get('/some/path/main.nf'), + '_nf_script_1b751fe91b751fe9': Paths.get('/other/path/module.nf'), + '_nf_script_1234567812345678': Paths.get("$pwd/foo/script.nf") ] expect: @@ -53,11 +53,11 @@ class LoggerHelperTest extends Specification { EXPECTED | LINE null | 'at nextflow.script.ScriptRunner.run(ScriptRunner.groovy:289)' null | 'at nextflow.script.BaseScript.run(BaseScript.groovy:151)' - ['/some/path/main.nf', '63'] | 'at Script_f1bbc0ef.runScript(Script_f1bbc0eff1bbc0ef:63)' - null | 'at Script_1b751fe9$_runScript_closure1.doCall(Script_1b751fe91b751fe9)' - ['/other/path/module.nf', '10'] | 'at Script_1b751fe9$_runScript_closure1.doCall(Script_1b751fe91b751fe9:10)' - ['foo/script.nf', '55'] | 'at Script_12345678.runScript(Script_1234567812345678:55)' - null | 'at Script_12345678.runScript(Script_xxxxxxxxxxxxxxxx:63)' + ['/some/path/main.nf', '63'] | 'at _nf_script_f1bbc0ef.runScript(_nf_script_f1bbc0eff1bbc0ef:63)' + null | 'at Script_1b751fe9$_runScript_closure1.doCall(_nf_script_1b751fe91b751fe9)' + ['/other/path/module.nf', '10'] | 'at _nf_script_1b751fe9$_runScript_closure1.doCall(_nf_script_1b751fe91b751fe9:10)' + ['foo/script.nf', '55'] | 'at _nf_script_12345678.runScript(_nf_script_1234567812345678:55)' + null | 'at _nf_script_12345678.runScript(_nf_script_xxxxxxxxxxxxxxxx:63)' } 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..b6f8421e0e --- /dev/null +++ b/modules/nextflow/src/testFixtures/groovy/test/ScriptHelper.groovy @@ -0,0 +1,323 @@ +/* + * 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 { + + /** + * Load a config from source text. + * + * @param text + */ + static Map loadConfig(String text) { + return ConfigParserFactory.create().parse(text) + } + + /** + * Load a script from source text. + * + * This function compiles and executes the script without + * running the pipeline. The entry workflow is executed + * (i.e. to construct the workflow DAG) unless opts.module + * is enabled. + * + * @param opts + * @param 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() + } + + /** + * Load a script from source text and return the TaskProcessor + * for the last parsed process. + * + * The script should declare a single process. + * + * @param scriptText + * @param binding + */ + static TaskProcessor parseAndReturnProcess(String scriptText, Map binding = null) { + def session = new TestSession() + session.setBinding(binding ? new ScriptBinding(binding) : new ScriptBinding()) + + session.init(null) + + ScriptLoaderFactory.create(session) + .parse(scriptText) + .runScript() + + session.fireDataflowNetwork() + + 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() + } + } + + /** + * Execute a script from source text, with an optional config map. + * + * This function compiles and executes the script, launches a + * pipeline run, and waits for the run to finish. It returns the + * last statement of the entry workflow, which can be used to pass + * output channels to a test. + * + * @param text + * @param config + */ + 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 + } + + /** + * Execute a script from a source file, with an optional config map. + * + * This function compiles and executes the script, launches a + * pipeline run, and waits for the run to finish. It returns the + * last statement of the entry workflow, which can be used to pass + * output channels to a test. + * + * @param opts + * @param path + */ + 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..b8451e4622 100644 --- a/modules/nf-commons/src/test/nextflow/plugin/extension/PluginExtensionMethodsTest.groovy +++ b/modules/nf-commons/src/test/nextflow/plugin/extension/PluginExtensionMethodsTest.groovy @@ -21,11 +21,14 @@ import java.nio.file.Path import nextflow.Channel import nextflow.exception.DuplicateModuleFunctionException +import nextflow.exception.MissingProcessException import nextflow.plugin.Plugins import nextflow.plugin.TestPluginManager import spock.lang.Shared import spock.lang.TempDir import test.Dsl2Spec + +import static test.ScriptHelper.* /** * * @author Jorge Aguilera @@ -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,32 +178,33 @@ 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) + thrown(MissingProcessException) } 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 deleted file mode 100644 index 732be8a9b0..0000000000 --- a/modules/nf-commons/src/test/nextflow/plugin/hello/HelloDslTest.groovy +++ /dev/null @@ -1,70 +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.plugin.hello - -import nextflow.Channel -import nextflow.plugin.extension.PluginExtensionProvider -import spock.lang.Specification -import spock.lang.Timeout -import test.MockScriptRunner - - -/** - * @author : jorge - * - */ -@Timeout(10) -class HelloDslTest extends Specification{ - - def setup () { - def ext = new PluginExtensionProvider(){ - def loadForTest(){ - install() - loadPluginExtensionMethods("nf-foo", new HelloExtension(), [reverse:'reverse', goodbye:'goodbye']) - } - } - ext.loadForTest() - } - - def 'should perform a hi and create a channel' () { - when: - def SCRIPT = ''' - channel.reverse('hi!') - ''' - and: - def result = new MockScriptRunner([:]).setScript(SCRIPT).execute() - then: - result.val == '!ih' - result.val == Channel.STOP - } - - def 'should store a goodbye' () { - when: - def SCRIPT = ''' - channel - .of('Bye bye folks') - .goodbye() - ''' - and: - def result = new MockScriptRunner([:]).setScript(SCRIPT).execute() - then: - result.val == 'Bye bye folks' - result.val == Channel.STOP - - } -} diff --git a/modules/nf-commons/src/testFixtures/groovy/nextflow/plugin/hello/HelloExtension.groovy b/modules/nf-commons/src/testFixtures/groovy/nextflow/plugin/hello/HelloExtension.groovy index bbfc82a514..216b4bc613 100644 --- a/modules/nf-commons/src/testFixtures/groovy/nextflow/plugin/hello/HelloExtension.groovy +++ b/modules/nf-commons/src/testFixtures/groovy/nextflow/plugin/hello/HelloExtension.groovy @@ -28,6 +28,7 @@ import nextflow.NF import nextflow.Session import nextflow.extension.CH import nextflow.extension.DataflowHelper +import nextflow.plugin.extension.Factory import nextflow.plugin.extension.Function import nextflow.plugin.extension.Operator import nextflow.plugin.extension.PluginExtensionPoint @@ -50,7 +51,7 @@ class HelloExtension extends PluginExtensionPoint { private Session session /* - * nf-core initializes the plugin once loaded and session is ready + * Nextflow initializes the plugin once loaded and session is ready * @param session */ @Override @@ -65,7 +66,7 @@ class HelloExtension extends PluginExtensionPoint { * - it's public * - it returns a DataflowWriteChannel * - * nf-core will inspect the extension class and allow the script to call all these kind of methods + * Nextflow will inspect the extension class and allow the script to call all these kind of methods * * the method can require arguments but it's not mandatory, it depends of the business logic of the method * @@ -115,7 +116,7 @@ class HelloExtension extends PluginExtensionPoint { */ @Operator String goodbyeWrongSignature(DataflowReadChannel source) { - source.val + 'goodbye' } protected DataflowWriteChannel createReverseChannel(final String message){ 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 4b48c451f8..d378f54744 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 @@ -35,9 +35,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; @@ -143,6 +145,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); @@ -153,7 +158,7 @@ public void visitWorkflow(WorkflowNode node) { "nextflow.script.BodyDef", args( closureX(null, main), - constX(getSourceText(node)), + constX(null), constX("workflow") ) )); @@ -217,25 +222,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() @@ -251,32 +260,6 @@ public void visitOutputs(OutputBlockNode node) { moduleNode.addStatement(result); } - private String getSourceText(WorkflowNode node) { - if( node.isEntry() && node.getLineNumber() == -1 ) - return sgh.getSourceText(node.main); - - var builder = new StringBuilder(); - var colx = node.getColumnNumber(); - var colz = node.getLastColumnNumber(); - var first = node.getLineNumber(); - var last = node.getLastLineNumber(); - for( int i = first; i <= last; i++ ) { - var line = sourceUnit.getSource().getLine(i, null); - if( i == last ) { - line = line.substring(0, colz-1).replaceFirst("}.*$", ""); - if( line.trim().isEmpty() ) - continue; - } - if( i == first ) { - line = line.substring(colx-1).replaceFirst("^.*\\{", "").trim(); - if( line.isEmpty() ) - continue; - } - builder.append(line).append('\n'); - } - return builder.toString(); - } - private void syntaxError(ASTNode node, String message) { sourceUnit.addError(new SyntaxException(message, node)); } 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..91750807f9 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 @@ -159,9 +159,8 @@ private void declareMethod(MethodNode mn) { if( otherInclude != null ) { vsc.addError("`" + name + "` is already included", mn, "First included here", otherInclude); } - var otherMethods = cn.getDeclaredMethods(name); - if( otherMethods.size() > 0 ) { - var other = otherMethods.get(0); + var other = firstConflictingMethod(mn, cn); + if( other != null ) { var first = mn.getLineNumber() < other.getLineNumber() ? mn : other; var second = mn.getLineNumber() < other.getLineNumber() ? other : mn; vsc.addError("`" + name + "` is already declared", second, "First declared here", first); @@ -170,6 +169,13 @@ private void declareMethod(MethodNode mn) { cn.addMethod(mn); } + private static MethodNode firstConflictingMethod(MethodNode mn, ClassNode cn) { + return cn.getDeclaredMethods(mn.getName()).stream() + .filter(other -> !(mn instanceof FunctionNode) || !(other instanceof FunctionNode)) + .findFirst() + .orElse(null); + } + public void visit() { var moduleNode = sourceUnit.getAST(); if( moduleNode instanceof ScriptNode sn ) { diff --git a/tests-v1/checks/.PARSER-V1 b/tests-v1/checks/.PARSER-V1 index 0584272ed2..7472126088 100644 --- a/tests-v1/checks/.PARSER-V1 +++ b/tests-v1/checks/.PARSER-V1 @@ -1,5 +1,4 @@ # ADDITIONAL TESTS TO VERIFY THE V1 PARSER -chunk.nf complex-names.nf env-out.nf env2.nf diff --git a/validation/azure.config b/validation/azure.config index 7e80bc53ed..ce59720a50 100644 --- a/validation/azure.config +++ b/validation/azure.config @@ -12,15 +12,8 @@ process { workDir = 'az://my-data/work' azure { - storage { - accountName = "$AZURE_STORAGE_ACCOUNT_NAME" - accountKey = "$AZURE_STORAGE_ACCOUNT_KEY" - } - batch { location = 'westeurope' - accountName = "$AZURE_BATCH_ACCOUNT_NAME" - accountKey = "$AZURE_BATCH_ACCOUNT_KEY" allowPoolCreation = true pools { 'nextflow-ci' { diff --git a/validation/wave-tests/example1/nextflow.config b/validation/wave-tests/example1/nextflow.config index 07caade354..2cb0d54a04 100644 --- a/validation/wave-tests/example1/nextflow.config +++ b/validation/wave-tests/example1/nextflow.config @@ -2,7 +2,3 @@ docker { enabled = true } -tower { - accessToken = "$TOWER_ACCESS_TOKEN" -} - diff --git a/validation/wave-tests/example3/nextflow.config b/validation/wave-tests/example3/nextflow.config index 38a0e07f6c..8de358b19f 100644 --- a/validation/wave-tests/example3/nextflow.config +++ b/validation/wave-tests/example3/nextflow.config @@ -1,7 +1,3 @@ -tower { - accessToken = "$TOWER_ACCESS_TOKEN" -} - wave { enabled = true strategy = ['conda']