Skip to content

Commit cf3c68a

Browse files
Merge pull request #166 from hyanwong/windows-compilation
Skip failing windows unittests, and omit max_rss on windows (+ correctly report for OS X)
2 parents 98ee2ec + c04a6c9 commit cf3c68a

File tree

7 files changed

+81
-14
lines changed

7 files changed

+81
-14
lines changed

appveyor.yml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ install:
2020
build_script:
2121
- cmd: python setup.py build_ext --inplace
2222
- cmd: pip install daiquiri # daiquiri isn't available on conda-forge
23-
# Cannot run tests for now because LMDB isn't found.
24-
# - cmd: nosetests -vs
23+
- cmd: nosetests -vs
2524

2625
after_test:
2726
- cmd: python setup.py bdist_wheel

requirements/conda-minimal.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ tskit
77
msprime
88
zarr
99
lmdb
10+
python-lmdb
1011
sortedcontainers
1112
attrs
1213
# Needed for evaluation script.

tests/test_cli.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,8 @@ def test_infer(self):
154154
self.run_command(["infer", self.sample_file, "-O", output_trees])
155155
self.verify_output(output_trees)
156156

157+
@unittest.skipIf(sys.platform == "win32",
158+
"windows simultaneous file permissions issue")
157159
def test_nominal_chain(self):
158160
output_trees = os.path.join(self.tempdir.name, "output.trees")
159161
self.run_command(["generate-ancestors", self.sample_file])
@@ -166,6 +168,8 @@ def test_verify(self):
166168
self.run_command(["infer", self.sample_file, "-O", output_trees])
167169
self.run_command(["verify", self.sample_file, output_trees])
168170

171+
@unittest.skipIf(sys.platform == "win32",
172+
"windows simultaneous file access permissions issue")
169173
def test_augment_ancestors(self):
170174
output_trees = os.path.join(self.tempdir.name, "output.trees")
171175
augmented_ancestors = os.path.join(
@@ -196,6 +200,8 @@ def test_infer(self):
196200
output_trees = os.path.join(self.tempdir.name, "output.trees")
197201
self.run_command(["infer", self.sample_file, "-O", output_trees])
198202

203+
@unittest.skipIf(sys.platform == "win32",
204+
"windows simultaneous file permissions issue")
199205
def test_nominal_chain(self):
200206
output_trees = os.path.join(self.tempdir.name, "output.trees")
201207
self.run_command(["generate-ancestors", self.sample_file])
@@ -212,6 +218,8 @@ class TestMatchSamples(TestCli):
212218
"""
213219
Tests for the match samples options.
214220
"""
221+
@unittest.skipIf(sys.platform == "win32",
222+
"windows simultaneous file permissions issue")
215223
def test_no_simplify(self):
216224
output_trees = os.path.join(self.tempdir.name, "output.trees")
217225
output_trees_no_simplify = os.path.join(
@@ -283,4 +291,8 @@ def test_list_unknown_files(self):
283291
for bad_file in [zero_file]:
284292
self.assertRaises(
285293
exceptions.FileFormatError, self.run_command, ["list", bad_file])
286-
self.assertRaises(IsADirectoryError, self.run_command, ["list", "/"])
294+
if sys.platform == "win32":
295+
# Windows raises a PermissionError not IsADirectoryError when opening a dir
296+
self.assertRaises(PermissionError, self.run_command, ["list", "/"])
297+
else:
298+
self.assertRaises(IsADirectoryError, self.run_command, ["list", "/"])

tests/test_formats.py

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
Tests for the data files.
2121
"""
2222

23+
import sys
2324
import unittest
2425
import tempfile
2526
import os.path
@@ -42,10 +43,16 @@ class DataContainerMixin(object):
4243
Common tests for the the data container classes."
4344
"""
4445
def test_load(self):
45-
self.assertRaises(IsADirectoryError, self.tested_class.load, "/")
4646
self.assertRaises(
4747
FileNotFoundError, self.tested_class.load, "/file/does/not/exist")
48-
bad_format_files = ["LICENSE", "/dev/urandom"]
48+
if sys.platform != "win32":
49+
self.assertRaises(IsADirectoryError, self.tested_class.load, "/")
50+
bad_format_files = ["LICENSE", "/dev/urandom"]
51+
else:
52+
# Windows raises a PermissionError not IsADirectoryError when opening a dir
53+
self.assertRaises(PermissionError, self.tested_class.load, "/")
54+
# No /dev/urandom on Windows
55+
bad_format_files = ["LICENSE"]
4956
for bad_format_file in bad_format_files:
5057
self.assertTrue(os.path.exists(bad_format_file))
5158
self.assertRaises(
@@ -98,6 +105,8 @@ def verify_data_round_trip(self, ts, input_file):
98105
the_age = ts.node(variant.site.mutations[0].node).time
99106
self.assertEqual(the_age, age[j])
100107

108+
@unittest.skipIf(sys.platform == "win32",
109+
"windows simultaneous file permissions issue")
101110
def test_defaults_with_path(self):
102111
ts = self.get_example_ts(10, 10)
103112
with tempfile.TemporaryDirectory(prefix="tsinf_format_test") as tempdir:
@@ -137,6 +146,8 @@ def test_chunk_size(self):
137146
if name.endswith("genotypes"):
138147
self.assertEqual(array.chunks[1], chunk_size)
139148

149+
@unittest.skipIf(sys.platform == "win32",
150+
"windows simultaneous file permissions issue")
140151
def test_filename(self):
141152
ts = self.get_example_ts(14, 15)
142153
with tempfile.TemporaryDirectory(prefix="tsinf_format_test") as tempdir:
@@ -150,6 +161,8 @@ def test_filename(self):
150161
self.assertIsNot(other_input_file, input_file)
151162
self.assertEqual(other_input_file, input_file)
152163

164+
@unittest.skipIf(sys.platform == "win32",
165+
"windows simultaneous file permissions issue")
153166
def test_chunk_size_file_equal(self):
154167
ts = self.get_example_ts(13, 15)
155168
with tempfile.TemporaryDirectory(prefix="tsinf_format_test") as tempdir:
@@ -640,6 +653,8 @@ def test_copy_new_uuid(self):
640653
self.assertNotEqual(copy.uuid, data.uuid)
641654
self.assertTrue(copy.data_equal(data))
642655

656+
@unittest.skipIf(sys.platform == "win32",
657+
"windows simultaneous file permissions issue")
643658
def test_copy_update_inference_sites(self):
644659
with formats.SampleData() as data:
645660
for j in range(4):
@@ -687,6 +702,8 @@ def set_value(data, value):
687702
data.finalise()
688703
self.assertRaises(ValueError, set_value, data, [])
689704

705+
@unittest.skipIf(sys.platform == "win32",
706+
"windows simultaneous file permissions issue")
690707
def test_overwrite_partial(self):
691708
# Check that we can correctly overwrite partially written and
692709
# unfinalised files. See
@@ -791,6 +808,8 @@ def test_defaults_no_path(self):
791808
for _, array in ancestor_data.arrays():
792809
self.assertEqual(array.compressor, None)
793810

811+
@unittest.skipIf(sys.platform == "win32",
812+
"windows simultaneous file permissions issue")
794813
def test_defaults_with_path(self):
795814
sample_data, ancestors = self.get_example_data(10, 10, 40)
796815
with tempfile.TemporaryDirectory(prefix="tsinf_format_test") as tempdir:
@@ -835,6 +854,8 @@ def test_chunk_size(self):
835854
self.assertEqual(ancestor_data.ancestors_end.chunks, (chunk_size,))
836855
self.assertEqual(ancestor_data.ancestors_age.chunks, (chunk_size,))
837856

857+
@unittest.skipIf(sys.platform == "win32",
858+
"windows simultaneous file permissions issue")
838859
def test_filename(self):
839860
sample_data, ancestors = self.get_example_data(10, 2, 40)
840861
with tempfile.TemporaryDirectory(prefix="tsinf_format_test") as tempdir:
@@ -848,6 +869,8 @@ def test_filename(self):
848869
self.assertIsNot(other_ancestor_data, ancestor_data)
849870
self.assertEqual(other_ancestor_data, ancestor_data)
850871

872+
@unittest.skipIf(sys.platform == "win32",
873+
"windows simultaneous file permissions issue")
851874
def test_chunk_size_file_equal(self):
852875
N = 60
853876
sample_data, ancestors = self.get_example_data(22, 16, N)
@@ -910,6 +933,8 @@ def test_add_ancestor_errors(self):
910933
start=0, end=num_sites, age=1, focal_sites=[0],
911934
haplotype=np.zeros(num_sites, dtype=np.uint8))
912935

936+
@unittest.skipIf(sys.platform == "win32",
937+
"windows simultaneous file permissions issue")
913938
def test_zero_sequence_length(self):
914939
# Mangle a sample data file to force a zero sequence length.
915940
ts = msprime.simulate(10, mutation_rate=2, random_seed=5)

tests/test_low_level.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
"""
2020
Integrity tests for the low-level module.
2121
"""
22+
import sys
2223
import unittest
2324

2425
import _tsinfer
@@ -29,11 +30,20 @@ class TestOutOfMemory(unittest.TestCase):
2930
Make sure we raise the correct error when out of memory occurs in
3031
the library code.
3132
"""
32-
def test_tree_sequence_builder(self):
33+
@unittest.skipIf(sys.platform == "win32",
34+
"windows seems to allow initializing with insane # of nodes"
35+
" (perhaps memory allocation is optimised out at this stage?)")
36+
def test_tree_sequence_builder_too_many_nodes(self):
3337
big = 2**62
3438
self.assertRaises(
3539
MemoryError, _tsinfer.TreeSequenceBuilder, num_sites=1, max_nodes=big,
3640
max_edges=1)
41+
42+
@unittest.skipIf(sys.platform == "win32",
43+
"windows raises an assert error not a memory error with 2**62 edges"
44+
" (line 149 of object_heap.c)")
45+
def test_tree_sequence_builder_too_many_edges(self):
46+
big = 2**62
3747
self.assertRaises(
3848
MemoryError, _tsinfer.TreeSequenceBuilder, num_sites=1, max_nodes=1,
3949
max_edges=big)

tsinfer/cli.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,16 @@
1919
"""
2020
Command line interfaces to tsinfer.
2121
"""
22+
import sys
2223
import argparse
2324
import os
2425
import os.path
2526
import logging
26-
import resource
2727
import math
28+
try:
29+
import resource
30+
except ImportError:
31+
resource = None # resource.getrusage absent on windows, so skip outputting max mem
2832

2933
import daiquiri
3034
import tskit
@@ -98,13 +102,19 @@ def get(self, key, total):
98102

99103
def summarise_usage():
100104
wall_time = humanize.naturaldelta(time.time() - __before)
101-
rusage = resource.getrusage(resource.RUSAGE_SELF)
102-
user_time = humanize.naturaldelta(rusage.ru_utime)
103-
sys_time = rusage.ru_stime
104-
max_rss = humanize.naturalsize(rusage.ru_maxrss * 1024, binary=True)
105+
user_time = humanize.naturaldelta(os.times().user)
106+
sys_time = os.times().system
107+
if resource is None:
108+
# Don't report max memory on Windows. We could do this using the psutil lib, via
109+
# psutil.Process(os.getpid()).get_ext_memory_info().peak_wset if demand exists
110+
maxmem_str = ""
111+
else:
112+
max_mem = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
113+
if sys.platform != 'darwin':
114+
max_mem *= 1024 # Linux and other OSs (e.g. freeBSD) report maxrss in kb
115+
maxmem_str = "; max memory={}".format(humanize.naturalsize(max_mem, binary=True))
105116
logger.info("wall time = {}".format(wall_time))
106-
logger.info("rusage: user={}; sys={:.2f}s; max_rss={}".format(
107-
user_time, sys_time, max_rss))
117+
logger.info("rusage: user={}; sys={:.2f}s".format(user_time, sys_time) + maxmem_str)
108118

109119

110120
def get_default_path(path, input_path, extension):

tsinfer/formats.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import datetime
2424
import itertools
2525
import logging
26+
import sys
2627
import os
2728
import os.path
2829
import queue
@@ -308,7 +309,16 @@ def _new_lmdb_store(self):
308309
os.unlink(self.path)
309310
# The existence of a lock-file can confuse things, so delete it.
310311
remove_lmdb_lockfile(self.path)
311-
return zarr.LMDBStore(self.path, subdir=False)
312+
if sys.platform == "win32":
313+
# Currently lmdb windows allocates the entire file size rather than growing
314+
# dynamically(see see https://github.com/mozilla/lmdb-rs/issues/40).
315+
# On windows, we therefore hard code a smaller map_size to avoid filling up
316+
# disk space. This is clearly a hack just to get tsinfer working on win32.
317+
# Arbitrarily, we allocate 1GiB (2**30 bytes). Windows users who want to run
318+
# large inferences may need to increase the hardcoded map_size below.
319+
return zarr.LMDBStore(self.path, map_size=2**30, subdir=False)
320+
else:
321+
return zarr.LMDBStore(self.path, subdir=False)
312322

313323
@classmethod
314324
def load(cls, path):

0 commit comments

Comments
 (0)