Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nested params and json/yaml files #50

Merged
merged 10 commits into from
Nov 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ output
.nextflow
.nextflow.*
.settings
params.json
test_mock.nf
/.nf-test/
site
Expand Down
67 changes: 62 additions & 5 deletions docs/testcases/global_variables.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,63 @@
# Global Variables
# Params and Global Variables

## outputDir
## Params

The `params` block is optional and is a simple map that can be used to overwrite Nextflow's input `params`. The `params` block is located in the `when` block of a testcase. You can set params manually:

```Groovy
when {
params {
outdir = "output"
}
}
```

It is also possible to set nested params using the same syntax as in your Nextflow script:

```Groovy
when {
params {
output {
dir = "output"
}
}
}
```

In addition, you can load the `params` from a JSON file:

```Groovy
when {
params {
load("$baseDir/tests/params.json")
}
}
```

or from a YAML file:

```Groovy
when {
params {
load("$baseDir/tests/params.yaml")
}
}
```

nf-test allows to combine both techniques and therefor it is possible to overwrite one or more `params` from the json file:

```Groovy
when {
params {
load("$baseDir/tests/params.json")
outputDir = "new/output/path"
}
}
```

## Global Variables

### `outputDir`

This variable points to the directory within the temporary test directory (`.nf-test/tests/<test-dir>/output/`). The variable can be set under params:

Expand All @@ -10,14 +67,14 @@ params {
}
```

## baseDir
### `baseDir`

This variable points to the directory to locate the base directory of the main nf-test config. The variable can be used e.g. in the process definition:
This variable points to the directory to locate the base directory of the main nf-test config. The variable can be used e.g. in the process definition to build absolute paths for input files:

```Groovy
process {
"""
f1 = file('$baseDir/tests/input/file123.gz')
file1 = file("$baseDir/tests/input/file123.gz")
"""
}
```
11 changes: 3 additions & 8 deletions example/hello.nf.test
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,9 @@ nextflow_pipeline {
test("hello world example should start 4 processes") {

expect {
with(workflow){
assert success
assert trace.tasks().size() == 4
assert "Ciao world!" in stdout
assert "Bonjour world!" in stdout
assert "Hello world!" in stdout
assert "Hola world!" in stdout
}
assert workflow.success
assert snapshot(workflow).match()

}

}
Expand Down
2 changes: 1 addition & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ nav:
- Workflow Testing: testcases/nextflow_workflow.md
- Process Testing: testcases/nextflow_process.md
- Function Testing: testcases/nextflow_function.md
- Global Variables: testcases/global_variables.md
- Params and Global Variables: testcases/global_variables.md
- Writing Assertions:
- Power Assertions: assertions/assertions.md
- Files: assertions/files.md
Expand Down
161 changes: 157 additions & 4 deletions src/main/java/com/askimed/nf/test/lang/ParamsMap.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
package com.askimed.nf.test.lang;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

import org.codehaus.groovy.control.CompilationFailedException;

import groovy.json.JsonSlurper;
import groovy.lang.Closure;
import groovy.lang.Writable;
import groovy.text.SimpleTemplateEngine;
import groovy.yaml.YamlSlurper;

public class ParamsMap extends HashMap<String, Object> {

private static final long serialVersionUID = 1L;

public String baseDir = "lukas";
public String baseDir = "";

public String outputDir = "";

public void setBaseDir(String baseDir) {
Expand All @@ -20,13 +34,152 @@ public void setOutputDir(String outputDir) {
put("outputDir", outputDir);
}


public String getBaseDir() {
return baseDir;
}

public String getOutputDir() {
return outputDir;
}

public void load(String filename) throws CompilationFailedException, ClassNotFoundException, IOException {
load(new File(filename));
}

public void load(File file) throws CompilationFailedException, ClassNotFoundException, IOException {

if (file.getName().endsWith(".json")) {

loadFromJsonFile(file);

} else if (file.getName().endsWith(".yaml") || file.getName().endsWith(".yml")) {

loadFromYamlFile(file);

} else {

throw new RuntimeException(
"Could no load params from file '" + file.getAbsolutePath() + "': Unsupported file format");

}
}

public void loadFromJsonFile(File file) throws CompilationFailedException, ClassNotFoundException, IOException {

JsonSlurper jsonSlurper = new JsonSlurper();
Map<String, Object> map = (Map<String, Object>) jsonSlurper.parse(file);
map.putAll(this);

loadFromMap(map);

}

public void loadFromYamlFile(File file) throws CompilationFailedException, ClassNotFoundException, IOException {

YamlSlurper yamlSlurper = new YamlSlurper();
Map<String, Object> map = (Map<String, Object>) yamlSlurper.parse(new FileReader(file));
map.putAll(this);

loadFromMap(map);

}

public synchronized void loadFromMap(Map<String, Object> map)
throws CompilationFailedException, ClassNotFoundException, IOException {

evaluteTemplates(map);
putAll(map);

}

protected synchronized void evaluteTemplates(Map<String, Object> map)
throws CompilationFailedException, ClassNotFoundException, IOException {

SimpleTemplateEngine engine = new SimpleTemplateEngine();

Queue<Map<String, Object>> queue = new LinkedList<Map<String, Object>>();
queue.add(map);

while (queue.size() > 0) {

Map<String, Object> item = queue.remove(); // Pop Item

for (String key : item.keySet()) {

Object value = item.get(key);

if (value instanceof String) {

String template = value.toString();
Map<String, Object> binding = new HashMap<String, Object>(map);
Writable evaluatedTemplate = engine.createTemplate(template).make(binding);
item.put(key, evaluatedTemplate.toString());

} else if (value instanceof Map) {

Map<String, Object> nestedMap = createNestedMap((Map<String, Object>) value);
queue.add(nestedMap); // Add to queue instead of recurse
item.put(key, nestedMap);

} else {

item.put(key, value);

}
}

}

}

public void evaluateNestedClosures() {

evaluateNestedClosures(this);

}

protected void evaluateNestedClosures(Map<String, Object> map) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could also be written iteratively:

	protected void evaluateNestedClosures(Map<String, Object> map) {

        Queue< Map<String, Object> > queue = new LinkedList< Map<String, Object> >();
        queue.add(map);

        while (queue.size() > 0) {
            Map<String, Object> item = queue.remove();

            for (String key : item.keySet()) {
                Object value = item.get(key);

                if (!(value instanceof Closure)) continue; 

                Map<String, Object> nestedMap = createNestedMap();
                Closure closure = (Closure) value;
                closure.setDelegate(nestedMap);
                closure.setResolveStrategy(Closure.DELEGATE_FIRST);
                closure.call();
                item.put(key, nestedMap);
                
                queue.add(nestedMap); // Instead of recursion
    
            }

        }
	}


Queue<Map<String, Object>> queue = new LinkedList<Map<String, Object>>();
queue.add(map);

while (queue.size() > 0) {

Map<String, Object> item = queue.remove();

for (String key : item.keySet()) {
Object value = item.get(key);

if (!(value instanceof Closure))
continue;

Map<String, Object> nestedMap = createNestedMap();
Closure closure = (Closure) value;
closure.setDelegate(nestedMap);
closure.setResolveStrategy(Closure.DELEGATE_FIRST);
closure.call();
item.put(key, nestedMap);

queue.add(nestedMap); // Instead of recursion

}

}

}

protected Map<String, Object> createNestedMap() {
return createNestedMap(null);
}

protected Map<String, Object> createNestedMap(Map<String, Object> map) {
Map<String, Object> nestedMap = new HashMap<String, Object>();
nestedMap.put("baseDir", baseDir);
nestedMap.put("outputDir", outputDir);
if (map != null) {
nestedMap.putAll(map);
}
return nestedMap;
}

}
18 changes: 12 additions & 6 deletions src/main/java/com/askimed/nf/test/lang/TestContext.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.askimed.nf.test.lang;

import java.io.IOException;

import org.codehaus.groovy.control.CompilationFailedException;

import com.askimed.nf.test.core.ITest;
import com.askimed.nf.test.lang.extensions.Snapshot;
import com.askimed.nf.test.lang.function.Function;
Expand Down Expand Up @@ -72,19 +76,15 @@ public void params(Closure closure) {
this.paramsClosure = closure;
}

public void evaluateParamsClosure(String baseDir, String outputDir) {
params.setBaseDir(baseDir);
params.setOutputDir(outputDir);
this.baseDir = baseDir;
this.outputDir = outputDir;

public void evaluateParamsClosure() {
if (paramsClosure == null) {
return;
}
paramsClosure.setDelegate(params);
paramsClosure.setResolveStrategy(Closure.DELEGATE_FIRST);
paramsClosure.call();
paramsClosure.getMetaClass().getProperties();
params.evaluateNestedClosures();

}

Expand Down Expand Up @@ -144,8 +144,14 @@ public Snapshot snapshot(Object ... object ) {
}

public void init(String baseDir, String outputDir) {
params.setBaseDir(baseDir);
params.setOutputDir(outputDir);
this.baseDir = baseDir;
this.outputDir = outputDir;
}

public void loadParams(String filename) throws CompilationFailedException, ClassNotFoundException, IOException {
params.load(filename);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@ public static Object getJson(Path self) throws FileNotFoundException, IOExceptio
/* YAML */

public static Object getYaml(Path self) throws FileNotFoundException, IOException {
YamlSlurper YamlSlurper = new YamlSlurper();
return YamlSlurper.parse(self);
YamlSlurper yamlSlurper = new YamlSlurper();
return yamlSlurper.parse(self);
}

/* File methods */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ public void execute() throws Throwable {
when.execute(context);
}

context.evaluateParamsClosure(baseDir, outputDir.getAbsolutePath());
context.evaluateParamsClosure();
context.evaluateFunctionClosure();

// Create workflow mock
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ public void execute() throws Throwable {
when.execute(context);
}

context.evaluateParamsClosure(baseDir, outputDir.getAbsolutePath());
context.evaluateParamsClosure();
context.evaluateProcessClosure();

if (debug) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public void execute() throws Throwable {
when.execute(context);
}

context.evaluateParamsClosure(baseDir, outputDir.getAbsolutePath());
context.evaluateParamsClosure();
context.evaluateProcessClosure();

// Create workflow mock
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public void execute() throws Throwable {
when.execute(context);
}

context.evaluateParamsClosure(baseDir, outputDir.getAbsolutePath());
context.evaluateParamsClosure();
context.evaluateWorkflowClosure();

// Create workflow mock
Expand Down