diff --git a/package.json b/package.json index 2e9b34c..0ac4e7e 100644 --- a/package.json +++ b/package.json @@ -5,11 +5,16 @@ "main": "example-client.js", "scripts": { "test": "ts-mocha 'src/**/*.test.ts'", + "test:fno2cwl": "ts-mocha 'src/FnO2CWL.test.ts'", + "test:fno2cwl:watch": "ts-mocha 'src/FnO2CWL.test.ts' -w --watch-files src/FnO2CWL.test.ts", "build:typescript": "npx tsc index.ts", "setup:download-rmlmapper": "curl -L https://github.com/RMLio/rmlmapper-java/releases/download/v4.11.0/rmlmapper.jar --output rmlmapper.jar" }, "author": "Lander Noterman ", - "contributors": [ "Gertjan De Mulder", "Ben De Meester" ], + "contributors": [ + "Gertjan De Mulder", + "Ben De Meester" + ], "license": "ISC", "dependencies": { "@rmlio/rmlmapper-java-wrapper": "^2.0.2", diff --git a/resources/fno-cwl/example01/.ipynb_checkpoints/abstract-wf-checkpoint.cwl b/resources/fno-cwl/example01/.ipynb_checkpoints/abstract-wf-checkpoint.cwl new file mode 100644 index 0000000..65a0546 --- /dev/null +++ b/resources/fno-cwl/example01/.ipynb_checkpoints/abstract-wf-checkpoint.cwl @@ -0,0 +1,27 @@ +cwlVersion: v1.2 +class: Workflow + + +inputs: + message: string +outputs: [] + +steps: + echo: + run: ../echo.cwl + in: + message: message + out: [out] + # Here you know you want an operation that changes the case of + # the previous step, but you do not have an implementation yet. + uppercase: + run: + class: Operation + inputs: + message: string + outputs: + uppercase_message: string + in: + message: + source: echo/out + out: [uppercase_message] \ No newline at end of file diff --git a/resources/fno-cwl/example01/.ipynb_checkpoints/abstract-wf.cwl-checkpoint.ttl b/resources/fno-cwl/example01/.ipynb_checkpoints/abstract-wf.cwl-checkpoint.ttl new file mode 100644 index 0000000..f113d7b --- /dev/null +++ b/resources/fno-cwl/example01/.ipynb_checkpoints/abstract-wf.cwl-checkpoint.ttl @@ -0,0 +1,52 @@ +@prefix CommandOutputBinding: . +@prefix Workflow: . +@prefix cwl: . +@prefix ns1: . +@prefix rdf: . +@prefix sld: . +@prefix xsd: . + + a cwl:Workflow ; + ns1:original_cwlVersion "v1.2" ; + Workflow:steps , + ; + cwl:cwlVersion cwl:v1.2 ; + cwl:inputs . + + cwl:in ; + cwl:out ; + cwl:run . + + cwl:source . + + cwl:in ; + cwl:out ; + cwl:run [ a cwl:Operation ; + cwl:inputs ; + cwl:outputs ] . + + cwl:source . + + sld:type xsd:string . + + sld:type xsd:string . + + a cwl:CommandLineTool ; + ns1:original_cwlVersion "v1.2" ; + cwl:baseCommand ( "echo" ) ; + cwl:cwlVersion cwl:v1.2 ; + cwl:inputs ; + cwl:outputs ; + cwl:stdout "output.txt" . + + cwl:inputBinding [ ] ; + sld:type xsd:string . + + cwl:outputBinding [ CommandOutputBinding:glob "output.txt" ; + CommandOutputBinding:outputEval "$(self[0].contents)" ; + cwl:loadContents true ] ; + sld:type xsd:string . + + sld:type xsd:string . + + diff --git a/resources/fno-cwl/example01/.ipynb_checkpoints/echo-checkpoint.cwl b/resources/fno-cwl/example01/.ipynb_checkpoints/echo-checkpoint.cwl new file mode 100644 index 0000000..6248ae8 --- /dev/null +++ b/resources/fno-cwl/example01/.ipynb_checkpoints/echo-checkpoint.cwl @@ -0,0 +1,18 @@ +cwlVersion: v1.2 +class: CommandLineTool + +baseCommand: echo + +stdout: output.txt + +inputs: + message: + type: string + inputBinding: {} +outputs: + out: + type: string + outputBinding: + glob: output.txt + loadContents: true + outputEval: $(self[0].contents) diff --git a/resources/fno-cwl/example01/abstract-wf.cwl b/resources/fno-cwl/example01/abstract-wf.cwl new file mode 100644 index 0000000..81713d4 --- /dev/null +++ b/resources/fno-cwl/example01/abstract-wf.cwl @@ -0,0 +1,30 @@ +cwlVersion: v1.2 +class: Workflow + + +inputs: + message: string +outputs: + wf_output: + type: string + outputSource: uppercase/uppercase_message + +steps: + echo: + run: ./echo.cwl + in: + message: message + out: [out] + # Here you know you want an operation that changes the case of + # the previous step, but you do not have an implementation yet. + uppercase: + run: + class: Operation + inputs: + message: string + outputs: + uppercase_message: string + in: + message: + source: echo/out + out: [uppercase_message] \ No newline at end of file diff --git a/resources/fno-cwl/example01/abstract-wf.cwl.ttl b/resources/fno-cwl/example01/abstract-wf.cwl.ttl new file mode 100644 index 0000000..a903bb2 --- /dev/null +++ b/resources/fno-cwl/example01/abstract-wf.cwl.ttl @@ -0,0 +1,56 @@ +@prefix CommandOutputBinding: . +@prefix Workflow: . +@prefix cwl: . +@prefix ns1: . +@prefix rdf: . +@prefix sld: . +@prefix xsd: . + + a cwl:Workflow ; + ns1:original_cwlVersion "v1.2" ; + Workflow:steps , + ; + cwl:cwlVersion cwl:v1.2 ; + cwl:inputs ; + cwl:outputs . + + cwl:in ; + cwl:out ; + cwl:run . + + cwl:source . + + cwl:in ; + cwl:out ; + cwl:run [ a cwl:Operation ; + cwl:inputs ; + cwl:outputs ] . + + cwl:source . + + sld:type xsd:string . + + sld:type xsd:string . + + cwl:outputSource ; + sld:type xsd:string . + + a cwl:CommandLineTool ; + ns1:original_cwlVersion "v1.2" ; + cwl:baseCommand ( "echo" ) ; + cwl:cwlVersion cwl:v1.2 ; + cwl:inputs ; + cwl:outputs ; + cwl:stdout "output.txt" . + + cwl:inputBinding [ ] ; + sld:type xsd:string . + + cwl:outputBinding [ CommandOutputBinding:glob "output.txt" ; + CommandOutputBinding:outputEval "$(self[0].contents)" ; + cwl:loadContents true ] ; + sld:type xsd:string . + + sld:type xsd:string . + + diff --git a/resources/fno-cwl/example01/concrete-wf.cwl b/resources/fno-cwl/example01/concrete-wf.cwl new file mode 100644 index 0000000..0f6fda4 --- /dev/null +++ b/resources/fno-cwl/example01/concrete-wf.cwl @@ -0,0 +1,25 @@ +cwlVersion: v1.2 +class: Workflow + + +inputs: + message: string +outputs: + wf_output: + type: string + outputSource: uppercase/uppercase_message + +steps: + echo: + run: ./echo.cwl + in: + message: message + out: [out] + # Here you know you want an operation that changes the case of + # the previous step, but you do not have an implementation yet. + uppercase: + run: ./uppercase.cwl + in: + message: + source: echo/out + out: [uppercase_message] \ No newline at end of file diff --git a/resources/fno-cwl/example01/concrete-wf.cwl.ttl b/resources/fno-cwl/example01/concrete-wf.cwl.ttl new file mode 100644 index 0000000..48c9d20 --- /dev/null +++ b/resources/fno-cwl/example01/concrete-wf.cwl.ttl @@ -0,0 +1,64 @@ +@prefix CommandOutputBinding: . +@prefix ExpressionTool: . +@prefix Workflow: . +@prefix cwl: . +@prefix ns1: . +@prefix rdf: . +@prefix sld: . +@prefix xsd: . + + a cwl:Workflow ; + ns1:original_cwlVersion "v1.2" ; + Workflow:steps , + ; + cwl:cwlVersion cwl:v1.2 ; + cwl:inputs ; + cwl:outputs . + + cwl:in ; + cwl:out ; + cwl:run . + + cwl:source . + + cwl:in ; + cwl:out ; + cwl:run . + + cwl:source . + + cwl:outputSource ; + sld:type xsd:string . + + a cwl:CommandLineTool ; + ns1:original_cwlVersion "v1.2" ; + cwl:baseCommand ( "echo" ) ; + cwl:cwlVersion cwl:v1.2 ; + cwl:inputs ; + cwl:outputs ; + cwl:stdout "output.txt" . + + cwl:inputBinding [ ] ; + sld:type xsd:string . + + cwl:outputBinding [ CommandOutputBinding:glob "output.txt" ; + CommandOutputBinding:outputEval "$(self[0].contents)" ; + cwl:loadContents true ] ; + sld:type xsd:string . + + a cwl:ExpressionTool ; + ns1:original_cwlVersion "v1.2" ; + ExpressionTool:expression """${ return {"uppercase_message": inputs.message.toUpperCase()}; } +""" ; + cwl:cwlVersion cwl:v1.2 ; + cwl:inputs ; + cwl:outputs ; + cwl:requirements [ a cwl:InlineJavascriptRequirement ] . + + sld:type xsd:string . + + sld:type xsd:string . + + sld:type xsd:string . + + diff --git a/resources/fno-cwl/example01/cwl2fno-composition-mappings_result.ttl b/resources/fno-cwl/example01/cwl2fno-composition-mappings_result.ttl new file mode 100644 index 0000000..77e3ca9 --- /dev/null +++ b/resources/fno-cwl/example01/cwl2fno-composition-mappings_result.ttl @@ -0,0 +1,42 @@ +@prefix CommandOutputBinding: . +@prefix Workflow: . +@prefix cwl: . +@prefix ex: . +@prefix fno: . +@prefix fnoc: . +@prefix ns1: . +@prefix rdf: . +@prefix rdfs: . +@prefix sld: . +@prefix xsd: . + + + rdf:type fnoc:Composition ; + fnoc:composedOf [ fnoc:mapFrom [ ex:source ; + ex:sourceTool + ] ; + fnoc:mapTo [ ex:sink ; + ex:sinkTool + ] + ] ; + fnoc:composedOf [ fnoc:mapFrom [ ex:source ; + ex:sourceTool + ] ; + fnoc:mapTo [ ex:sink ; + ex:sinkTool + ] + ] ; + fnoc:composedOf [ fnoc:mapFrom [ ex:source ; + ex:sourceTool + ] ; + fnoc:mapTo [ ex:sink ; + ex:sinkTool + ] + ] ; + fnoc:composedOf [ fnoc:mapFrom [ ex:source ; + ex:sourceTool + ] ; + fnoc:mapTo [ ex:sink ; + ex:sinkTool + ] + ] . diff --git a/resources/fno-cwl/example01/cwl2fno-expected-result-concrete-wf.ttl b/resources/fno-cwl/example01/cwl2fno-expected-result-concrete-wf.ttl new file mode 100644 index 0000000..31be0d0 --- /dev/null +++ b/resources/fno-cwl/example01/cwl2fno-expected-result-concrete-wf.ttl @@ -0,0 +1,233 @@ +# Example: Concrete CWL Workflow to FnO +# Input: concrete-wf.cwl.ttl + +# CWL specs +@prefix CommandOutputBinding: . +@prefix Workflow: . +@prefix cwl: . +@prefix sld: . + +# FnO +@prefix fno: . +@prefix fnoc: . +@prefix fnom: . +@prefix fnoi: . +@prefix fns: . + +# Other +@prefix ex: . +@prefix ns1: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + +# Base +@base . + +# CWL documents +@prefix wf: . +@prefix t_echo: . +@prefix t_uc: . + +################################################################################ +# CWL Inputs -> FnO Parameters + +# echo input +t_echo:messageParameter + a fno:Parameter ; + fno:type xsd:string; + fno:predicate t_echo:message; +. + +# uppercase input +t_uc:messageParameter + a fno:Parameter ; + fno:type xsd:string; + fno:predicate t_uc:message; +. + +# workflow input +wf:messageParameter + a fno:Parameter ; + fno:type xsd:string ; + fno:predicate wf:message ; +. + +################################################################################ +# CWL Outputs -> FnO Outputs + +# echo output +t_echo:returnOutput + a fno:Output ; + fno:type xsd:string; + fno:predicate t_echo:out; +. + +# uppercase output +t_uc:returnOutput + a fno:Output ; + fno:type xsd:string; + fno:predicate t_uc:uppercase_message ; +. + +# workflow output +wf:returnOutput + a fno:Output ; + fno:type xsd:string; + fno:predicate wf:wf_output; +. + +################################################################################ +# CWL Processes -> FnO Functions + +# process: workflow +wf:Function + a fno:Function; + fno:expects ( wf:messageParameter ); + fno:returns ( wf:returnOutput ); +. + +# process: echo +t_echo:Function + a fno:Function; + fno:expects ( t_echo:messageParameter ); + fno:returns ( t_echo:returnOutput ); +. + +# process: uppercase +t_uc:Function + a fno:Function; + fno:expects ( t_uc:messageParameter ); + fno:returns ( t_uc:returnOutput ); +. + + +################################################################################ +# CWL Workflow -> Fno Composition + +wf:Composition + a fnoc:Composition ; + + fnoc:composedOf [ + # wf.in -> echo.in + fnoc:mapFrom [ + fnoc:constituentFunction wf:Function ; + fnoc:functionParameter wf:messageParameter ; + + ]; + + fnoc:mapTo [ + fnoc:constituentFunction t_echo:Function ; + fnoc:functionParameter t_echo:messageParameter ; + ]; + ], + # echo.out -> uppercase.in + [ + fnoc:mapFrom [ + fnoc:constituentFunction t_echo:Function ; + fnoc:functionOutput t_echo:returnOutput ; + + ]; + + fnoc:mapTo [ + fnoc:constituentFunction t_uc:Function ; + fnoc:functionParameter t_uc:messageParameter ; + ]; + ], + # uppercase.out -> wf.out + [ + fnoc:mapFrom [ + fnoc:constituentFunction t_uc:Function ; + fnoc:functionOutput t_uc:returnOutput ; + + ]; + + fnoc:mapTo [ + fnoc:constituentFunction wf:Function ; + fnoc:functionOutput wf:returnOutput ; + ]; + ] + +. + +################################################################################ +# FnO Implementations + +<#dev-platform> + a fnoi:Platform ; + fnoi:architecture "arm64" ; # uname -m + fnoi:operatingSystem "Darwin" ; # uname -s +. + +t_echo:Implementation + a fno:Implementation, fnoi:RuntimeProcess ; + fnoi:baseCommand "echo" ; + fnoi:platform <#dev-platform> ; + # TODO: arguments + +. + +t_uc:Implementation + a fno:Implementation, fnoi:RuntimeProcess ; + # TODO: baseCommand + # TODO: arguments + fnoi:platform <#dev-platform> ; +. + +################################################################################ +# FnO Mappings + +## Parameter mappings + +t_echo:messageParameterMapping + a fno:ParameterMapping, fnom:PositionParameterMapping ; + fnom:functionParameter t_echo:messageParameter ; + fnom:implementationParameterPosition 0; +. + + +# $ command arg1 -i data +# arg1 +# property: null | -N, -1, 0, 1, .., N |  +# value: +# $ command [artgs] [keyword args] +# $ command [keyword args] + + +t_uc:messageParameterMapping + a fno:ParameterMapping, fnom:PositionParameterMapping ; + fnom:functionParameter t_uc:messageParameter ; + fnom:implementationParameterPosition 0; +. + +## Output mappings + +t_echo:returnOutputputMapping + a fno:ReturnMapping, fnom:DefaultReturnMapping; + fnom:functionOutput t_echo:returnOutput ; +. + +t_uc:outputMapping + a fno:ReturnMapping, fnom:DefaultReturnMapping; + fnom:functionOutput t_uc:returnOutput ; +. + +# fno:Mapping + +t_echo:Mapping + a fno:Mapping ; + fno:function t_echo:Function ; + fno:implementation t_echo:Implementation ; # TODO: introduce a new fnoi:CommandLineTool class? + fno:parameterMapping t_echo:messageParameterMapping ; + fno:returnMapping t_echo:returnOutputputMapping ; + # fno:methodMapping <..> ; # TODO: delete? +. + +t_uc:Mapping + a fno:Mapping ; + fno:function t_uc:Function ; + fno:implementation t_uc:Implementation ; # TODO: introduce a new fnoi:CommandLineTool class? + fno:parameterMapping t_uc:messageParameterMapping ; + fno:returnMapping t_uc:outputMapping ; + # fno:methodMapping <..> ; # TODO: delete? +. \ No newline at end of file diff --git a/resources/fno-cwl/example01/cwl2fno_result.ttl b/resources/fno-cwl/example01/cwl2fno_result.ttl new file mode 100644 index 0000000..74d6249 --- /dev/null +++ b/resources/fno-cwl/example01/cwl2fno_result.ttl @@ -0,0 +1,62 @@ +@prefix CommandOutputBinding: . +@prefix Workflow: . +@prefix cwl: . +@prefix ex: . +@prefix fno: . +@prefix fnoc: . +@prefix ns1: . +@prefix rdf: . +@prefix rdfs: . +@prefix sld: . +@prefix xsd: . + + + rdf:type fno:Parameter ; + ex:parent "abstract-wf.cwl" ; + fno:type xsd:string . + + + rdf:type fno:Output ; + ex:parent "abstract-wf.cwl" ; + fno:type xsd:string . + + + rdf:type fnoc:Composition ; + fnoc:composedOf [ fnoc:mapFrom [ ex:source ] ; + fnoc:mapTo [ ex:sink ] + ] ; + fnoc:composedOf [ fnoc:mapFrom [ ex:source ] ; + fnoc:mapTo [ ex:sink ] + ] ; + fnoc:composedOf [ fnoc:mapFrom [ ex:source ] ; + fnoc:mapTo [ ex:sink ] + ] ; + fnoc:composedOf [ fnoc:mapFrom [ ex:source ] ; + fnoc:mapTo [ ex:sink ] + ] . + + + rdf:type fno:Parameter ; + ex:parent "echo.cwl" ; + fno:type xsd:string . + + + rdf:type fno:Function ; + ex:label "abstract-wf.cwl" ; + fno:expects ( ) ; + fno:expects ( ) ; + fno:returns ( ) ; + fno:returns ( ) . + + + rdf:type fno:Function ; + ex:label "echo.cwl" ; + fno:expects ( ) ; + fno:expects ( ) ; + fno:returns ( ) ; + fno:returns ( ) . + + + rdf:type fno:Output ; + ex:parent "echo.cwl" ; + fno:type xsd:string . diff --git a/resources/fno-cwl/example01/echo.cwl b/resources/fno-cwl/example01/echo.cwl new file mode 100644 index 0000000..6248ae8 --- /dev/null +++ b/resources/fno-cwl/example01/echo.cwl @@ -0,0 +1,18 @@ +cwlVersion: v1.2 +class: CommandLineTool + +baseCommand: echo + +stdout: output.txt + +inputs: + message: + type: string + inputBinding: {} +outputs: + out: + type: string + outputBinding: + glob: output.txt + loadContents: true + outputEval: $(self[0].contents) diff --git a/resources/fno-cwl/example01/uppercase.cwl b/resources/fno-cwl/example01/uppercase.cwl new file mode 100644 index 0000000..eb0b818 --- /dev/null +++ b/resources/fno-cwl/example01/uppercase.cwl @@ -0,0 +1,13 @@ +cwlVersion: v1.2 +class: ExpressionTool + +requirements: + InlineJavascriptRequirement: {} + +inputs: + message: string +outputs: + uppercase_message: string + +expression: | + ${ return {"uppercase_message": inputs.message.toUpperCase()}; } diff --git a/resources/fno-cwl/toUpperCase/abstract.cwl b/resources/fno-cwl/toUpperCase/abstract.cwl new file mode 100644 index 0000000..8d1d74d --- /dev/null +++ b/resources/fno-cwl/toUpperCase/abstract.cwl @@ -0,0 +1,6 @@ +cwlVersion: v1.2 +class: Operation +inputs: + message: string +outputs: + uppercase_message: string \ No newline at end of file diff --git a/resources/fno-cwl/toUpperCase/abstract.fno.ttl b/resources/fno-cwl/toUpperCase/abstract.fno.ttl new file mode 100644 index 0000000..f1d361e --- /dev/null +++ b/resources/fno-cwl/toUpperCase/abstract.fno.ttl @@ -0,0 +1,29 @@ +@prefix dcterms: . +@prefix fno: . +@prefix fnoi: . +@prefix fns: . +@prefix owl: . +@prefix rdf: . +@prefix rdfs: . +@prefix xsd: . + +fns:upperCaseFunction + a fno:Function ; + fno:name "to Uppercase" ; + rdfs:label "to Uppercase" ; + dcterms:description "Returns the input with all letters in upper case." ; + fno:expects ( fns:stringParameter ) ; + fno:returns ( fns:uppercaseOutput ) . + +fns:stringParameter rdf:type fno:Parameter ; + rdfs:label "x"@en ; + fno:predicate fns:x ; + fno:required "true"^^xsd:boolean . + +fns:uppercaseOutput rdf:type fno:Output ; + rdfs:label "uppercase output"@en ; + fno:predicate fns:out . + +# Alternative: link to published GREL description +# <#upperCaseFunction> +# owl:sameAs . diff --git a/resources/fno-cwl/toUpperCase/concrete.fno.ttl b/resources/fno-cwl/toUpperCase/concrete.fno.ttl new file mode 100644 index 0000000..d490052 --- /dev/null +++ b/resources/fno-cwl/toUpperCase/concrete.fno.ttl @@ -0,0 +1,42 @@ +@prefix ex: . +@prefix fno: . +@prefix fnoi: . +@prefix fnom: . +@prefix fns: . + +fns:abstractToConcreteMapping + a fno:Mapping; + fno:function fns:upperCaseFunction; + fno:implementation fns:upperCaseFunctionImplementation; + fno:methodMapping fns:methodMapping ; + fno:parameterMapping fns:parameterMapping; + fno:returnMapping fns:returnMapping; +. + +fns:upperCaseFunctionImplementation + a fno:Implementation, fnoi:JavaScriptImplementation, fnoi:JavaScriptFunction; + ex:expression "function toUpperCase(x) { return x.toUpperCase() }" +. + +fns:methodMapping + a fno:MethodMapping, fnom:StringMethodMapping; + fnom:method-name "toUpperCase"; +. + + +# fns:parameterMapping # TODO: PropertyParameterMapping not yet supported by function-handler-js +# a fno:ParameterMapping, fnom:PropertyParameterMapping; +# fnom:functionParameter fns:stringParameter; +# fnom:implementationProperty "x"; +# . +fns:parameterMapping + a fno:ParameterMapping, fnom:PositionParameterMapping; + fnom:functionParameter fns:stringParameter; + fnom:implementationParameterPosition 0; +. + + +fns:returnMapping + a fno:ReturnMapping, fnom:DefaultReturnMapping; + fnom:functionOutput fns:uppercaseOutput; +. diff --git a/resources/fno-cwl/toUpperCase/concrete_expressiontool.cwl b/resources/fno-cwl/toUpperCase/concrete_expressiontool.cwl new file mode 100644 index 0000000..eb0b818 --- /dev/null +++ b/resources/fno-cwl/toUpperCase/concrete_expressiontool.cwl @@ -0,0 +1,13 @@ +cwlVersion: v1.2 +class: ExpressionTool + +requirements: + InlineJavascriptRequirement: {} + +inputs: + message: string +outputs: + uppercase_message: string + +expression: | + ${ return {"uppercase_message": inputs.message.toUpperCase()}; } diff --git a/src/FnO2CWL.test.ts b/src/FnO2CWL.test.ts new file mode 100644 index 0000000..ccaefde --- /dev/null +++ b/src/FnO2CWL.test.ts @@ -0,0 +1,145 @@ +import { expect } from 'chai'; +import { FunctionHandler } from './FunctionHandler'; +import { } from 'mocha'; +import { JavaScriptHandler } from './handlers/JavaScriptHandler'; +import prefixes from './prefixes'; +import * as fs from 'fs'; +import * as path from 'path'; +import { RuntimeProcessHandler } from './handlers/RuntimeProcessHandler'; + +function readFile(path) { + return fs.readFileSync(path, { encoding: 'utf-8' }); +} + +function writeFile(path, data) { + return fs.writeFileSync(path, data,{ encoding: 'utf-8' }); +} + +function prefix(...args) { + return args.join(''); +} + +const dirResources = path.resolve(__dirname, '../resources/fno-cwl'); +const basenameConcreteWorkflowCWL = 'cwl2fno-expected-result-concrete-wf' +const basenameConcreteWorkflowTurtle = `${basenameConcreteWorkflowCWL}.ttl` + +describe('Tests for example01', () => { // the tests container + + const dirContainerResources = path.resolve(dirResources, 'example01'); + const pathConcreteWorkflow = path.resolve(dirContainerResources, basenameConcreteWorkflowTurtle); + + const jsFunctionImplementations = { + + echo: function(message:string) { + console.log(`echo(${message})`) + return message; + }, + + uppercase: function(message:string) { + return message.toUpperCase(); + } + + } + + // Currently storing these specific prefixes/namespaces in a local object + const ns = { + gdm: "http://gddmulde.be#", + wf: "http://gddmulde.be/concrete-wf.cwl#", + t_echo: "http://gddmulde.be/echo.cwl#", + t_uc: "http://gddmulde.be/uppercase.cwl#", + } + + it('Contains FnO document of the concrete workflow',async () => { + expect(fs.existsSync(pathConcreteWorkflow)); + }); + + it('Contains FnO document of the abstract workflow',async () => { + throw Error('Not Yet Implemented'); + }); + + + /** + * Tests loading of functions within the composition (echo, uppercase) + * Tests results from executing echo and uppercase + * Test execution result of the function composition (i.e. the workflow) + */ + it.only('Correctly executes the concrete workflow',async () => { + const handler = new FunctionHandler(); + const rtpHandler = new RuntimeProcessHandler(); + const jsHandler = new JavaScriptHandler(); + + // Load FnO descriptions + const iriConcreteWorkflow = prefix(ns.gdm, 'workflowGraph'); + await handler.addFunctionResource( + iriConcreteWorkflow, + { + type: 'string', + contents: readFile(pathConcreteWorkflow), + contentType: 'text/turtle' + } + ); + + // IRIs + const iriWf = prefix(ns.wf, 'Function'); + const iriEcho = prefix(ns.t_echo, 'Function'); + const iriUppercase = prefix(ns.t_uc, 'Function'); + + // FnO Function objects + const fWf = await handler.getFunction(iriWf); + const fEcho = await handler.getFunction(iriEcho); + const fUppercase = await handler.getFunction(iriUppercase); + + // Test loaded function objects + expect(fWf).not.to.be.null; + expect(fWf.id).not.to.be.null; + + expect(fEcho).not.to.be.null; + expect(fEcho.id).not.to.be.null; + + expect(fUppercase).not.to.be.null; + expect(fUppercase.id).not.be.null; + + // Instantiate implementation handlers + const iriEchoImplementation = prefix(ns.t_echo, 'Implementation'); + const iriUppercaseImplementation = prefix(ns.t_uc, 'Implementation'); + + // Implementation by RuntimeProcessHandler + handler.implementationHandler.loadImplementation( + iriEchoImplementation, + rtpHandler, + { baseCommand: "echo" } // TODO: function object?? + ); + + // Implementation by JavascriptHandler + handler.implementationHandler.loadImplementation( + iriUppercaseImplementation, + jsHandler, + { fn: jsFunctionImplementations.uppercase } + ); + + // Test echo output + const fnEchoArgMap = { + [prefix(ns.t_echo, 'message')]: 'abc' + } + const fnEchoResult = await handler.executeFunction(fEcho, fnEchoArgMap); + expect(fnEchoResult[prefix(ns.t_echo, 'out')]).to.equal('abc\n'); + + // Test uppercase output + const fnUppercaseArgMap = { + [prefix(ns.t_uc, 'message')]: 'abc' + } + const fnUppercaseResult = await handler.executeFunction(fUppercase, fnUppercaseArgMap) + expect(fnUppercaseResult[prefix(ns.t_uc, 'uppercase_message')]).to.equal('ABC'); + + // Test result of execution the function composition + const wfArgMap = { + [prefix(ns.wf, 'message')]: 'abc' + } + const wfResult = await handler.executeFunction(fWf, wfArgMap); + expect(wfResult[prefix(ns.wf, 'wf_output')]).to.equal('ABC\n'); + + }); + + +}); + diff --git a/src/FunctionHandler.test.ts b/src/FunctionHandler.test.ts index b1162e9..d35b81a 100644 --- a/src/FunctionHandler.test.ts +++ b/src/FunctionHandler.test.ts @@ -482,3 +482,70 @@ describe('Workflow', () => { }); + +describe('FnO-CWL', () => { + const dirWorkflowResources = path.join(dirResources, 'fno-cwl'); + + const loadFunctionResource = (handler, iri: string, contents: any) => handler.addFunctionResource( + iri, + { + contents, + type: 'string', + contentType: 'text/turtle', + }, + ); + + const minimalFunctionTests = (f) => { + expect(f).not.to.be.null; + expect(f.id).not.to.be.null; + }; + + /** + * Creates a function handler for the given testcase. + * @param caseDir + */ + const setupCase = async (caseDir) => { + const handler = new FunctionHandler(); + console.log('setting up case: ' , caseDir) + // load abstract function description + await loadFunctionResource(handler, `${prefixes.fns}abstract`, readFile(path.resolve(caseDir, 'abstract.fno.ttl'))); + console.log('loaded abstract function description √'); + await loadFunctionResource(handler, `${prefixes.fns}concrete`, readFile(path.resolve(caseDir, 'concrete.fno.ttl'))); + console.log('loaded concrete function description √'); + + return handler; + }; + + /** + * + */ + it('toUpperCase', async () => { + // load composition resources + const dirName = 'toUpperCase'; + const caseDir = path.resolve(dirWorkflowResources, dirName); + // Setup handler for current testcase + const handler = await setupCase(caseDir); + // function objects + const fnToUpperCase = await handler.getFunction(`${prefixes.fns}upperCaseFunction`); + minimalFunctionTests(fnToUpperCase) + // Map function labels to JS implementations + const functionJavaScriptImplementations = { + upperCaseFunction: (x) => x.toUpperCase() + }; + // Load JS implementations + const jsHandler = new JavaScriptHandler(); + Object.entries(functionJavaScriptImplementations).forEach(([lbl, fn]) => { + console.log(`${prefixes.fns}${lbl}Implementation`); + + handler.implementationHandler.loadImplementation(`${prefixes.fns}${lbl}Implementation`, jsHandler, { fn }); + }); + const refArg0 = `${prefixes.fns}x`; + // Execute composition + const result = await handler.executeFunction(fnToUpperCase, { [refArg0]: 'abc' }); + expect(result[`${prefixes.fns}out`]).to.equal('ABC'); + }); + + + +}); + diff --git a/src/handlers/RuntimeProcessHandler.ts b/src/handlers/RuntimeProcessHandler.ts new file mode 100644 index 0000000..f587a02 --- /dev/null +++ b/src/handlers/RuntimeProcessHandler.ts @@ -0,0 +1,46 @@ +import { Handler } from './Handler'; +import prefixes from '../prefixes'; +import {exec} from "child_process"; +export class RuntimeProcessHandler extends Handler { + constructor() { + super(`${prefixes.fnoi}RuntimeProcessHandler`); + } + + async executeFunction(args: {[predicate: string]: any}, options: any): Promise<{ [key: string]: any }> { + const fnArgs: any[] = []; + + if (Object.keys(options.args.positionArgs).length > 0) { + const maxArg = Object.keys(options.args.positionArgs).map(k => Number(k)).sort().reverse()[0]; + for (let index = 0; index <= maxArg; index++) { + if (options.args.positionArgs[index]) { + fnArgs.push(args[options.args.positionArgs[index]]) + } else { + fnArgs.push(undefined); + } + } + } + + // Build command to execute + const cmd = [options.baseCommand, ...fnArgs].join(' '); + const execPromise = new Promise((resolve,reject)=>{ + exec(cmd, (error,stdout,stderr)=>{ + if (error) { + reject(error) + return; + } + if (stderr) { + reject(stderr); + return; + } + resolve(stdout) + }) + }) + + const fnResult = await execPromise; + const result = {}; + if (options.returns['_default']) { + result[options.returns['_default']] = fnResult; + } + return result; + } +} \ No newline at end of file