Skip to content

Commit e32062e

Browse files
committed
test fixture cleanup
remove not-run tests
1 parent a8128ab commit e32062e

File tree

2 files changed

+97
-135
lines changed

2 files changed

+97
-135
lines changed

docs/development.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -945,6 +945,60 @@ checks for code quality.
945945
- [CodeCov](https://codecov.io/gh)_ tracks test coverage in Python and C.
946946

947947

948+
### Testing methods on a broad range of tree sequences
949+
950+
It's important to test methods on "weird" examples beyond what msprime returns:
951+
polytomies, samples not all at the same time, samples that are parents to other samples,
952+
multiple mutations on the same edge and at the same time, etcetera. Small,
953+
discrete simulations with overlapping generations are a good way to produce these,
954+
and these can be produced by the `wf_sim` method in `tests/test_wright_fisher.py`
955+
(and other tools in `tests/tsutil.py`). The way these are applied across the tests
956+
is fairly hodge-podge; here are suggestions for best practices moving forward.
957+
958+
(1) Write a "naive" implementation of your method. This should be code that
959+
obviously does exactly what you want to compute but with no thought at all for
960+
efficiency. (2) Write a `validate` method that compares the output of the method
961+
to the naive version. (3) Run this on a few extremely simple edge case examples
962+
where you can also compare to an answer computed by hand. (4) Also run this on a
963+
suite of example simulations, including weird ones (see above).
964+
965+
An easy and clean way to run across a suite of example simulations is
966+
using [pytest's fixtures](https://docs.pytest.org/en/stable/how-to/fixtures.html).
967+
An example is in `test_tree_stats.py`: `wf_fixture_sims` is a fixture returning
968+
a dictionary of simulations. It has `scope="session"`, meaning each simulation
969+
will only be run once and the result cached. Using this is another fixture,
970+
`wf_fixture`, that has a list of `params` that are the keys to that dictionary.
971+
Any test that uses `wf_fixture` will then be run many times, once on each simulation,
972+
for instance simply by
973+
```
974+
def test_something(self, wf_fixture_sims):
975+
self.validate(wf_fixture_sims)
976+
```
977+
If, for instance, a test wants to use only the "unsimplified" and "no_deep_history"
978+
simulations, then you can do:
979+
```
980+
@pytest.mark.parametrize('key', ['unsimplified', 'no_deep_history'])
981+
def test_something(self, wf_fixture_sims, key):
982+
self.validate(wf_fixture_sims[key])
983+
```
984+
985+
TODO: move the `wf_fixture` to `conftest.py` so it can be used across modules,
986+
and make sure it includes All the Weirdnesses.
987+
A list of all currently defined fixtures can be found by running
988+
`pytest --fixtures tests`.
989+
990+
991+
### Running codecov locally
992+
993+
Sometimes it's nice to check for code coverage yourself without waiting for github.
994+
To do this for the python code, you can run
995+
```
996+
python -m pytest -x --cov=tskit --cov-report=html --cov-branch -n4 --durations=20 tests
997+
```
998+
The commands used to do this are found in `.github/workflows/test.yml`
999+
TODO: write down how to get coverage for C.
1000+
1001+
9481002
(sec_development_best_practices)=
9491003

9501004

python/tests/test_tree_stats.py

Lines changed: 43 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -667,24 +667,11 @@ def test_short_sequence_length(self):
667667
self.verify(ts)
668668

669669
@pytest.mark.slow
670-
def test_wright_fisher_unsimplified(self, wf_sim_fixture):
671-
self.verify(wf_sim_fixture["unsimplified"])
670+
def test_wright_fisher_slow(self, wf_fixture_slow):
671+
self.verify(wf_fixture_slow)
672672

673-
@pytest.mark.slow
674-
def test_wright_fisher_initial_generation(self, wf_sim_fixture):
675-
self.verify(wf_sim_fixture["initial_generation"])
676-
677-
def test_wright_fisher_initial_generation_no_deep_history(self, wf_sim_fixture):
678-
self.verify(wf_sim_fixture["no_deep_history"])
679-
680-
def test_wright_fisher_unsimplified_multiple_roots(self, wf_sim_fixture):
681-
self.verify(wf_sim_fixture["unsimplified_multi_roots"])
682-
683-
def test_wright_fisher_simplified(self, wf_sim_fixture):
684-
self.verify(wf_sim_fixture["simplified"])
685-
686-
def test_wright_fisher_simplified_multiple_roots(self, wf_sim_fixture):
687-
self.verify(wf_sim_fixture["simplified_multi_roots"])
673+
def test_wright_fisher(self, wf_fixture):
674+
self.verify(wf_fixture)
688675

689676
def test_empty_ts(self):
690677
tables = tskit.TableCollection(1.0)
@@ -782,7 +769,7 @@ def _make_ts(length):
782769

783770
# Wright-Fisher simulation fixtures
784771
@pytest.fixture(scope="session")
785-
def wf_sim_fixture():
772+
def wf_fixture_sims():
786773
"""Common Wright-Fisher simulations used across test classes."""
787774
# Pre-compute all common WF simulations
788775
simulations = {}
@@ -829,51 +816,37 @@ def wf_sim_fixture():
829816
return simulations
830817

831818

832-
@pytest.fixture(scope="session")
833-
def wf_mut_sim_fixture():
834-
"""Wright-Fisher simulations with mutations for MutatedTopologyExamplesMixin."""
835-
simulations = {}
836-
837-
# With mutations for site-based tests
838-
tables = wf.wf_sim(
839-
4, 5, seed=1, deep_history=True, initial_generation_samples=False, num_loci=10
840-
)
841-
tables.sort()
842-
ts = msprime.mutate(tables.tree_sequence(), rate=0.05, random_seed=234)
843-
simulations["unsimplified"] = ts
844-
845-
tables = wf.wf_sim(
846-
6, 5, seed=3, deep_history=True, initial_generation_samples=True, num_loci=2
847-
)
848-
tables.sort()
849-
tables.simplify()
850-
ts = msprime.mutate(tables.tree_sequence(), rate=0.08, random_seed=2)
851-
simulations["initial_generation"] = ts
852-
853-
tables = wf.wf_sim(
854-
7, 15, seed=202, deep_history=False, initial_generation_samples=True, num_loci=5
819+
@pytest.fixture(
820+
params=[
821+
"no_deep_history",
822+
"unsimplified_multi_roots",
823+
"simplified",
824+
"simplified_multi_roots",
825+
],
826+
scope="session",
827+
)
828+
def wf_fixture(wf_fixture_sims, request):
829+
"""
830+
A collection of small Wright-Fisher simulations.
831+
"""
832+
ts = msprime.sim_mutations(
833+
wf_fixture_sims[request.param], rate=0.05, random_seed=1234
855834
)
856-
tables.sort()
857-
tables.simplify()
858-
ts = msprime.mutate(tables.tree_sequence(), rate=0.1, random_seed=3)
859-
simulations["no_deep_history"] = ts
835+
assert ts.num_mutations > 0
836+
return ts
860837

861-
tables = wf.wf_sim(
862-
8, 15, seed=1, deep_history=False, initial_generation_samples=False, num_loci=20
863-
)
864-
tables.sort()
865-
ts = msprime.mutate(tables.tree_sequence(), rate=0.01, random_seed=2)
866-
simulations["unsimplified_multi_roots"] = ts
867838

868-
tables = wf.wf_sim(
869-
9, 10, seed=1, deep_history=True, initial_generation_samples=False, num_loci=5
839+
@pytest.fixture(params=["unsimplified", "initial_generation"], scope="session")
840+
def wf_fixture_slow(wf_fixture_sims, request):
841+
"""
842+
A few more small Wright-Fisher simulations. Despite the name, in total
843+
they take about the same time for tests together as wf_fixture.
844+
"""
845+
ts = msprime.sim_mutations(
846+
wf_fixture_sims[request.param], rate=0.05, random_seed=1234
870847
)
871-
tables.sort()
872-
ts = tables.tree_sequence().simplify()
873-
ts = tsutil.jukes_cantor(ts, 10, 0.01, seed=1)
874-
simulations["simplified"] = ts
875-
876-
return simulations
848+
assert ts.num_mutations > 0
849+
return ts
877850

878851

879852
@pytest.fixture(scope="session")
@@ -1133,28 +1106,13 @@ def test_many_trees_sequence_length_infinite_sites(
11331106
ts = ts_6_length_factory_fixture(L)
11341107
self.verify(ts)
11351108

1136-
def test_wright_fisher_unsimplified(self, wf_mut_sim_fixture):
1137-
ts = wf_mut_sim_fixture["unsimplified"]
1109+
def test_wright_fisher(self, wf_fixture):
1110+
ts = wf_fixture
11381111
assert ts.num_sites > 0
11391112
self.verify(ts)
11401113

1141-
def test_wright_fisher_initial_generation(self, wf_mut_sim_fixture):
1142-
ts = wf_mut_sim_fixture["initial_generation"]
1143-
assert ts.num_sites > 0
1144-
self.verify(ts)
1145-
1146-
def test_wright_fisher_initial_generation_no_deep_history(self, wf_mut_sim_fixture):
1147-
ts = wf_mut_sim_fixture["no_deep_history"]
1148-
assert ts.num_sites > 0
1149-
self.verify(ts)
1150-
1151-
def test_wright_fisher_unsimplified_multiple_roots(self, wf_mut_sim_fixture):
1152-
ts = wf_mut_sim_fixture["unsimplified_multi_roots"]
1153-
assert ts.num_sites > 0
1154-
self.verify(ts)
1155-
1156-
def test_wright_fisher_simplified(self, wf_mut_sim_fixture):
1157-
ts = wf_mut_sim_fixture["simplified"]
1114+
def test_wright_fisher_slow(self, wf_fixture_slow):
1115+
ts = wf_fixture_slow
11581116
assert ts.num_sites > 0
11591117
self.verify(ts)
11601118

@@ -4310,13 +4268,14 @@ def update_result(window_index, u, right):
43104268

43114269

43124270
def site_allele_frequency_spectrum(
4313-
ts, sample_sets, windows, polarised=False, span_normalise=True
4271+
ts, sample_sets, windows, time_windows=None, polarised=False, span_normalise=True
43144272
):
43154273
"""
43164274
Efficient implementation of the algorithm used as the basis for the
43174275
underlying C version.
43184276
"""
43194277
windows = ts.parse_windows(windows)
4278+
assert time_windows is None
43204279
num_windows = windows.shape[0] - 1
43214280
out_dim = [1 + len(sample_set) for sample_set in sample_sets]
43224281

@@ -4622,8 +4581,7 @@ class TestSampleSets(StatsTestCase):
46224581
Tests that passing sample sets in various ways gets interpreted correctly.
46234582
"""
46244583

4625-
def get_example_ts(self, ts_10_mut_recomb_fixture):
4626-
ts = ts_10_mut_recomb_fixture
4584+
def get_example_ts(self, ts):
46274585
assert ts.num_mutations > 0
46284586
return ts
46294587

@@ -7288,18 +7246,18 @@ def f(x):
72887246
x = naive_branch_general_stat(
72897247
ts, W, f, time_windows=[0, 0.5, 2.0], span_normalise=False
72907248
)
7291-
self.assertArrayAlmostEqual(x, true_x)
7249+
np.testing.assert_allclose(x, true_x)
72927250

72937251
x0 = branch_general_stat(ts, W, f, time_windows=None, span_normalise=False)
72947252
x1 = naive_branch_general_stat(
72957253
ts, W, f, time_windows=None, span_normalise=False
72967254
)
7297-
self.assertArrayAlmostEqual(x0, x1)
7255+
np.testing.assert_allclose(x0, x1)
72987256
x_tw = branch_general_stat(
72997257
ts, W, f, time_windows=[0, 0.5, 2.0], span_normalise=False
73007258
)
73017259

7302-
self.assertArrayAlmostEqual(x, x_tw)
7260+
np.testing.assert_allclose(x, x_tw)
73037261

73047262
def test_bad_time_windows(self, four_taxa_test_case):
73057263
ts = four_taxa_test_case
@@ -7372,58 +7330,8 @@ def test_drop_dimension(self, four_taxa_test_case, mode):
73727330

73737331
def test_four_taxon_example(self, four_taxa_test_case_afs):
73747332
ts, examples = four_taxa_test_case_afs
7375-
for k, (params, afs) in enumerate(examples):
7376-
print(k)
7333+
for params, afs in examples:
73777334
ts_afs = ts.allele_frequency_spectrum(**params)
73787335
py_afs = allele_frequency_spectrum(ts, **params)
73797336
np.testing.assert_allclose(afs, ts_afs)
73807337
np.testing.assert_allclose(afs, py_afs)
7381-
7382-
def test_decap_vs_tw(self):
7383-
ns = 2
7384-
time_grid = np.append(np.logspace(2, 5, 11), np.inf)[0:1]
7385-
ts = msprime.sim_ancestry(
7386-
ns,
7387-
recombination_rate=1e-8,
7388-
sequence_length=1e6,
7389-
population_size=1e4,
7390-
random_seed=1,
7391-
)
7392-
ts = msprime.sim_mutations(ts, rate=1e-8, random_seed=2)
7393-
sample_sets = [np.arange(ns), np.arange(ns, 2 * ns)]
7394-
7395-
time_grid = np.append(0, time_grid)
7396-
time_grid = np.append(time_grid, np.inf)
7397-
7398-
windows = np.array([0, 0.5, 1]) * ts.sequence_length
7399-
windows = None
7400-
test1 = ts.allele_frequency_spectrum(
7401-
sample_sets=sample_sets,
7402-
time_windows=time_grid,
7403-
mode="branch",
7404-
polarised=True,
7405-
span_normalise=True,
7406-
windows=windows,
7407-
).cumsum(axis=0)
7408-
7409-
test2 = branch_allele_frequency_spectrum(
7410-
ts,
7411-
windows=windows,
7412-
sample_sets=sample_sets,
7413-
time_windows=time_grid,
7414-
polarised=True,
7415-
span_normalise=True,
7416-
).cumsum(axis=0)
7417-
7418-
# manually calculate twAFS
7419-
twafs0 = np.zeros((time_grid.size, ns + 1, ns + 1))
7420-
for i, t in enumerate(time_grid):
7421-
tsd = ts.decapitate(t) if t < np.inf else ts
7422-
twafs0[i] = tsd.allele_frequency_spectrum(
7423-
sample_sets=sample_sets,
7424-
mode="branch",
7425-
polarised=True,
7426-
span_normalise=True,
7427-
).squeeze()
7428-
self.assertArrayAlmostEqual(test1, test2)
7429-
self.assertArrayAlmostEqual(test2, twafs0[1:])

0 commit comments

Comments
 (0)