Skip to content

Commit 4ae77ad

Browse files
authored
Merge pull request #742 from common-workflow-language/windows-Jenkinsfile
Better windows support
2 parents 78f6271 + 6732ebe commit 4ae77ad

13 files changed

+150
-35
lines changed

Jenkinsfile

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
pipeline {
2+
agent {
3+
node {
4+
label 'windows'
5+
}
6+
7+
}
8+
stages {
9+
stage('build') {
10+
steps {
11+
withPythonEnv(pythonInstallation: 'Windows-CPython-36') {
12+
pybat(script: 'pip install .', returnStdout: true)
13+
pybat 'jenkins.bat'
14+
}
15+
16+
}
17+
}
18+
}
19+
post {
20+
always {
21+
junit 'tests.xml'
22+
23+
}
24+
25+
}
26+
}

Makefile

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ pylint_report.txt: ${PYSOURCES}
114114
diff_pylint_report: pylint_report.txt
115115
diff-quality --violations=pylint pylint_report.txt
116116

117-
.coverage: tests
117+
.coverage: testcov
118118

119119
coverage: .coverage
120120
coverage report
@@ -135,7 +135,11 @@ diff-cover.html: coverage-gcovr.xml coverage.xml
135135
--html-report diff-cover.html
136136

137137
## test : run the ${MODULE} test suite
138-
test: $(PYSOURCES)
138+
test: $(pysources)
139+
python setup.py test
140+
141+
## testcov : run the ${MODULE} test suite and collect coverage
142+
testcov: $(pysources)
139143
python setup.py test --addopts "--cov cwltool"
140144

141145
sloccount.sc: ${PYSOURCES} Makefile

appveyor.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,13 @@ install:
4646
build_script:
4747
- "%PYTHON%\\python.exe -m pip install ."
4848

49-
5049
test_script:
51-
- "%PYTHON%\\python.exe -m pytest --verbose -p no:cacheprovider"
50+
- "%PYTHON%\\python.exe -m pytest --verbose -p no:cacheprovider --junit-xml=tests.xml"
51+
52+
after_test:
53+
- ps: |
54+
$wc = New-Object 'System.Net.WebClient'
55+
$wc.UploadFile("https://ci.appveyor.com/api/testresults/junit/$($Env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests.xml))
5256
5357
branches:
5458
only:

cwltool/docker.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,41 @@
2828

2929
found_images = set() # type: Set[Text]
3030
found_images_lock = threading.Lock()
31+
__docker_machine_mounts = None # type: List[Text]
32+
__docker_machine_mounts_lock = threading.Lock()
33+
34+
def _get_docker_machine_mounts(): # type: () -> List[Text]
35+
global __docker_machine_mounts
36+
if __docker_machine_mounts is None:
37+
with __docker_machine_mounts_lock:
38+
if 'DOCKER_MACHINE_NAME' not in os.environ:
39+
__docker_machine_mounts = []
40+
else:
41+
__docker_machine_mounts = [u'/' + line.split(None, 1)[0]
42+
for line in subprocess.check_output(
43+
['docker-machine', 'ssh',
44+
os.environ['DOCKER_MACHINE_NAME'],
45+
'mount', '-t', 'vboxsf'],
46+
universal_newlines=True).splitlines()]
47+
return __docker_machine_mounts
48+
49+
def _check_docker_machine_path(path): # type: (Text) -> None
50+
mounts = _get_docker_machine_mounts()
51+
if mounts:
52+
found = False
53+
for mount in mounts:
54+
if path.startswith(mount):
55+
found = True
56+
break
57+
if not found:
58+
raise WorkflowException(
59+
"Input path {path} is not in the list of host paths mounted "
60+
"into the Docker virtual machine named {name}. Already mounted "
61+
"paths: {mounts}.\n"
62+
"See https://docs.docker.com/toolbox/toolbox_install_windows/#optional-add-shared-directories"
63+
" for instructions on how to add this path to your VM.".format(path=path,
64+
name=os.environ["DOCKER_MACHINE_NAME"], mounts=mounts))
65+
3166

3267
class DockerCommandLineJob(ContainerCommandLineJob):
3368

@@ -162,6 +197,8 @@ def add_volumes(self, pathmapper, runtime, secret_store=None):
162197
host_outdir_tgt = None
163198
if vol.type in ("File", "Directory"):
164199
if not vol.resolved.startswith("_:"):
200+
_check_docker_machine_path(docker_windows_path_adjust(
201+
vol.resolved))
165202
runtime.append(u"--volume=%s:%s:ro" % (
166203
docker_windows_path_adjust(vol.resolved),
167204
docker_windows_path_adjust(vol.target)))

cwltool/factory.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,28 @@ def __init__(self,
3838
makeTool=workflow.defaultMakeTool, # type: tCallable[[Any], Process]
3939
# should be tCallable[[Dict[Text, Any], Any], Process] ?
4040
executor=None, # type: tCallable[...,Tuple[Dict[Text,Any], Text]]
41-
**execkwargs # type: Any
41+
makekwargs={}, # type: Dict[Any, Any]
42+
**execkwargs # type: Dict[Any, Any]
4243
):
4344
# type: (...) -> None
4445
self.makeTool = makeTool
4546
if executor is None:
4647
executor = SingleJobExecutor()
4748
self.executor = executor
4849

49-
kwargs = get_default_args()
50-
kwargs.pop("job_order")
51-
kwargs.pop("workflow")
52-
kwargs.pop("outdir")
53-
kwargs.update(execkwargs)
54-
self.execkwargs = kwargs
50+
newExecKwargs = get_default_args()
51+
newExecKwargs.pop("job_order")
52+
newExecKwargs.pop("workflow")
53+
newExecKwargs.pop("outdir")
54+
newExecKwargs.update(execkwargs)
55+
self.execkwargs = newExecKwargs
56+
self.makekwargs = makekwargs
5557

5658
def make(self, cwl):
5759
"""Instantiate a CWL object from a CWl document."""
5860
load = load_tool.load_tool(cwl, self.makeTool,
59-
strict=self.execkwargs.get("strict", True))
61+
strict=self.execkwargs.get("strict", True),
62+
kwargs=self.makekwargs)
6063
if isinstance(load, int):
6164
raise Exception("Error loading tool")
6265
return Callable(load, self)

jenkins.bat

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
set PATH=%PATH%;"C:\\Program Files\\Docker Toolbox\\"
2+
docker-machine start default
3+
REM Set the environment variables to use docker-machine and docker commands
4+
FOR /f "tokens=*" %%i IN ('docker-machine env --shell cmd default') DO %%i
5+
6+
python setup.py test --addopts "--junit-xml=tests.xml --cov-report xml --cov cwltool"
7+
pip install codecov
8+
codecov

tests/test_examples.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@
3030
from cwltool.main import main
3131
from cwltool.utils import onWindows
3232

33-
from .util import get_data, needs_docker
33+
from .util import (get_data, needs_docker, get_windows_safe_factory,
34+
windows_needs_docker)
3435

3536
sys.argv = ['']
3637

@@ -135,10 +136,11 @@ def test_params(self):
135136
self.assertEqual(expr.interpolate("$(foo[\"b\\'ar\"].baz) $(foo[\"b\\'ar\"].baz)", inputs), "true true")
136137
self.assertEqual(expr.interpolate("$(foo['b\\\"ar'].baz) $(foo['b\\\"ar'].baz)", inputs), "null null")
137138

138-
139139
class TestFactory(unittest.TestCase):
140+
141+
@windows_needs_docker
140142
def test_factory(self):
141-
f = cwltool.factory.Factory()
143+
f = get_windows_safe_factory()
142144
echo = f.make(get_data("tests/echo.cwl"))
143145
self.assertEqual(echo(inp="foo"), {"out": "foo\n"})
144146

tests/test_fetch.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from cwltool.main import main
1313
from cwltool.workflow import defaultMakeTool
1414
from cwltool.resolver import resolve_local
15+
from cwltool.utils import onWindows
1516

1617
if sys.version_info < (3, 4):
1718
from pathlib2 import Path
@@ -71,15 +72,25 @@ class ResolverTest(unittest.TestCase):
7172
def test_resolve_local(self):
7273
origpath = os.getcwd()
7374
os.chdir(os.path.join(get_data("")))
75+
def norm(uri):
76+
if onWindows():
77+
return uri.lower()
78+
else:
79+
return uri
7480
try:
7581
root = Path.cwd()
7682
rooturi = root.as_uri()
77-
self.assertEqual(rooturi+"/tests/echo.cwl", resolve_local(None, os.path.join("tests", "echo.cwl")))
78-
self.assertEqual(rooturi+"/tests/echo.cwl#main", resolve_local(None, os.path.join("tests", "echo.cwl")+"#main"))
79-
self.assertEqual(rooturi+"/tests/echo.cwl", resolve_local(None, str(root / "tests" / "echo.cwl")))
80-
# On Windows and Python 2.7, the left side of this test returns
81-
# file:///C:/ (uppercase drive letter) and the right side returns
82-
# file:///c:/ (lowercase drive letter) so force a lowercase comparison.
83-
self.assertEqual((rooturi+"/tests/echo.cwl#main").lower(), resolve_local(None, str(root / "tests" / "echo.cwl")+"#main").lower())
83+
self.assertEqual(norm(rooturi+"/tests/echo.cwl"),
84+
norm(resolve_local(None, os.path.join("tests",
85+
"echo.cwl"))))
86+
self.assertEqual(norm(rooturi+"/tests/echo.cwl#main"),
87+
norm(resolve_local(None, os.path.join("tests",
88+
"echo.cwl")+"#main")))
89+
self.assertEqual(norm(rooturi+"/tests/echo.cwl"),
90+
norm(resolve_local(None, str(root / "tests" /
91+
"echo.cwl"))))
92+
self.assertEqual(norm(rooturi+"/tests/echo.cwl#main"),
93+
norm(resolve_local(None, str(root / "tests" /
94+
"echo.cwl")+"#main")))
8495
finally:
8596
os.chdir(origpath)

tests/test_iwdr.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,16 @@
22

33
import cwltool
44
import cwltool.factory
5-
from .util import get_data
5+
from .util import get_data, get_windows_safe_factory, windows_needs_docker
66

77

88
class TestInitialWorkDir(unittest.TestCase):
99

10+
@windows_needs_docker
1011
def test_newline_in_entry(self):
1112
"""
1213
test that files in InitialWorkingDirectory are created with a newline character
1314
"""
14-
f = cwltool.factory.Factory()
15+
f = get_windows_safe_factory()
1516
echo = f.make(get_data("tests/wf/iwdr-entry.cwl"))
1617
self.assertEqual(echo(message="hello"), {"out": "CONFIGVAR=hello\n"})

tests/test_js_sandbox.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
exec_js_process)
1414
import cwltool.sandboxjs
1515
from cwltool.utils import onWindows
16-
from .util import get_data
16+
from .util import get_data, get_windows_safe_factory, windows_needs_docker
1717
import pytest
1818

1919

@@ -47,8 +47,9 @@ def test_is_javascript_installed(self):
4747

4848
class TestValueFrom(unittest.TestCase):
4949

50+
@windows_needs_docker
5051
def test_value_from_two_concatenated_expressions(self):
51-
f = cwltool.factory.Factory()
52+
f = get_windows_safe_factory()
5253
echo = f.make(get_data("tests/wf/vf-concat.cwl"))
5354
self.assertEqual(echo(), {u"out": u"a sting\n"})
5455

tests/test_parallel.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,26 @@
77
import cwltool.factory
88
from cwltool.executors import MultithreadedJobExecutor
99
from cwltool.utils import onWindows
10-
from .util import get_data
10+
from .util import get_data, get_windows_safe_factory, windows_needs_docker
1111

1212

1313
class TestParallel(unittest.TestCase):
14-
@pytest.mark.skipif(onWindows(),
15-
reason="Unexplainable behavior: cwltool on AppVeyor does not recognize cwlVersion"
16-
"in count-lines1-wf.cwl")
14+
@windows_needs_docker
1715
def test_sequential_workflow(self):
1816
test_file = "tests/wf/count-lines1-wf.cwl"
19-
f = cwltool.factory.Factory(executor=MultithreadedJobExecutor())
17+
f = get_windows_safe_factory(executor=MultithreadedJobExecutor())
2018
echo = f.make(get_data(test_file))
2119
self.assertEqual(echo(file1= {
2220
"class": "File",
2321
"location": get_data("tests/wf/whale.txt")
2422
}),
2523
{"count_output": 16})
2624

25+
@windows_needs_docker
2726
def test_scattered_workflow(self):
2827
test_file = "tests/wf/scatter-wf4.cwl"
2928
job_file = "tests/wf/scatter-job2.json"
30-
f = cwltool.factory.Factory(executor=MultithreadedJobExecutor())
29+
f = get_windows_safe_factory(executor=MultithreadedJobExecutor())
3130
echo = f.make(get_data(test_file))
3231
with open(get_data(job_file)) as job:
3332
self.assertEqual(echo(**json.load(job)), {'out': ['foo one three', 'foo two four']})

tests/test_toolargparse.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,6 @@ def test_help(self):
7373
f.close()
7474
self.assertEquals(main(["--debug", f.name, '--input',
7575
get_data('tests/echo.cwl')]), 0)
76-
self.assertEquals(main(["--debug", f.name, '--input',
77-
get_data('tests/echo.cwl')]), 0)
78-
7976

8077
@needs_docker
8178
def test_bool(self):

tests/util.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
11
from __future__ import absolute_import
22
import os
3+
import functools
34

45
from pkg_resources import (Requirement, ResolutionError, # type: ignore
56
resource_filename)
67
import distutils.spawn
78
import pytest
89

10+
from cwltool.utils import onWindows, windows_default_container_id
11+
from cwltool.factory import Factory
12+
13+
def get_windows_safe_factory(**execkwargs):
14+
if onWindows():
15+
makekwargs = {'find_default_container': functools.partial(
16+
force_default_container, windows_default_container_id),
17+
'use_container': True}
18+
execkwargs['default_container'] = windows_default_container_id
19+
else:
20+
makekwargs = {}
21+
return Factory(makekwargs=makekwargs, **execkwargs)
22+
23+
def force_default_container(default_container_id, builder):
24+
return default_container_id
25+
926
def get_data(filename):
1027
filename = os.path.normpath(
1128
filename) # normalizing path depending on OS or else it will cause problem when joining path
@@ -26,3 +43,8 @@ def get_data(filename):
2643
needs_docker = pytest.mark.skipif(not bool(distutils.spawn.find_executable('docker')),
2744
reason="Requires the docker executable on the "
2845
"system path.")
46+
47+
windows_needs_docker = pytest.mark.skipif(
48+
onWindows() and not bool(distutils.spawn.find_executable('docker')),
49+
reason="Running this test on MS Windows requires the docker executable "
50+
"on the system path.")

0 commit comments

Comments
 (0)