Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions .github/workflows/python-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: Python unit tests
# Runs the pytest suite in tests/ against the FastOMA python package.
# Tests marked `slow` only run on the nightly schedule and on manual dispatch.
on:
push:
branches:
- dev
pull_request:
schedule:
- cron: "17 3 * * *"
workflow_dispatch:

concurrency:
group: "${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}"
cancel-in-progress: true

jobs:
pytest:
# pinned: the golden snapshots of tests/test_fungi30.py need the mafft/FastTree versions of this image
runs-on: ubuntu-24.04
name: "pytest | python ${{ matrix.python-version }}"
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.12"]

steps:
- name: Check out code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: ${{ matrix.python-version }}

# same external tools as the runtime stage of the Dockerfile
- name: Install external tools
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends fasttree mafft mmseqs2
mafft --version || true
FastTree -expert 2>&1 | head -1 || true

- name: Install FastOMA
run: |
python -m pip install --upgrade pip
python -m pip install -e . pytest

- name: Run tests
env:
# fail rather than skip the golden snapshots if the tool versions changed
FASTOMA_REQUIRE_GOLDEN: "1"
run: |
if [ "${{ github.event_name }}" = "schedule" ] || [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
python -m pytest -ra
else
python -m pytest -ra -m "not slow"
fi
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@ archive
.gitignore
__pycache__
*.orig

# nf-test working files
.nf-test/
.nf-test.log
3 changes: 3 additions & 0 deletions nf-test.config
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ config {
// location for all nf-tests
testsDir "./"

// groovy helper classes available in all tests (e.g. FastomaOutput)
libDir "nf-tests/lib"

// nf-test directory including temporary files for each test
workDir System.getenv("NFT_WORKDIR") ?: ".nf-test"

Expand Down
61 changes: 20 additions & 41 deletions nf-tests/default.nf.test
Original file line number Diff line number Diff line change
Expand Up @@ -15,62 +15,41 @@ nextflow_pipeline {
}

then {

// Custom function to filter timestamp lines
def filterTimestamps = { file ->
return file.readLines()
.findAll { !it.contains('!date-run') && !it.contains('originVersion') }
}
def out = file(params.output_folder).toPath()

// stable_name: All files + folders in ${params.output_folder}/ with a stable name
def stable_name = getAllFilesFromDir(params.output_folder, relative: true, includeDir: true, ignore: ['stats/*.{html,json,txt}'])
// stable_path: All files in ${params.output_folder}/ with stable content
def stable_path = getAllFilesFromDir(params.output_folder, ignoreFile: 'nf-tests/.nftignore')
def timestamp_files = getAllFilesFromDir(params.output_folder, include:['hogmap/*.hogmap', "FastOMA_HOGs.orthoxml"]).collect { file ->
[name: file.name, md5: listToMD5(filterTimestamps(file))]
}
def csv_files = getAllFilesFromDir(params.output_folder, include:['RootHOGs.tsv', 'OrthologousGroups.tsv', 'orthologs.tsv.gz']).collect { file ->
def csv_data = path(file.path).csv(sep: '\t', header: file.name != 'orthologs.tsv.gz', decompress: file.name.endsWith('.gz'))
def header = csv_data.getColumnNames()
def sorted_rows = csv_data.sort().rows

// Convert back to list of strings
def lines = []
lines.add(header.join('\t')) // Add header line
sorted_rows.each { row ->
lines.add(header.collect { col -> row[col] ?: '' }.join('\t'))
}
return [name: file.name, md5: listToMD5(lines), lineCount: lines.size()]
}
def fixed_line_number_files = getAllFilesFromDir(params.output_folder, include:['RootHOGs.tsv', 'OrthologousGroups.tsv', 'orthologs.tsv.gz', "FastOMA_HOGs.orthoxml", 'hogmap/*.hogmap']).collect { file ->
def line_count
if (file.name.endsWith('.gz')) {
// Use try-with-resources for proper cleanup
new java.util.zip.GZIPInputStream(file.newInputStream()).withCloseable { gzipStream ->
line_count = gzipStream.readLines().size()
}
} else {
line_count = file.readLines().size()
}
return [name: file.name, lineCount: line_count]
// hogmap scores have precision issues across platforms, so only their size is pinned
def hogmap_line_counts = getAllFilesFromDir(params.output_folder, include: ['hogmap/*.hogmap']).collect { f ->
[name: f.name, lineCount: f.readLines().size()]
}

// HOG/OG numbers and row order are not stable, so the inferred orthology is
// compared through order- and id-independent views (nf-tests/lib/FastomaOutput.groovy)
def orthoxml = out.resolve('FastOMA_HOGs.orthoxml')
def geneRefCounts = FastomaOutput.geneRefCounts(orthoxml)

assertAll(
{ assert workflow.success},
{ assert workflow.success },
// every protein is in at most one HOG
{ assert geneRefCounts.findAll { it.value > 1 } == [:] },
{ assert snapshot(
// Number of successful tasks
workflow.trace.succeeded().size(),
// All stable path name, with a relative path
stable_name,
// All files with stable contents
stable_path,
// List of all files with timestamps filtered out
// does not yet work, as hogmap files have percision issues
// and FastOMA_HOGs is not completely deterministic
// timestamp_files

// files with fixed number of lines
fixed_line_number_files
hogmap_line_counts,
[
orthoxml_elements : FastomaOutput.elementCounts(orthoxml),
hogs : FastomaOutput.canonicalHogs(orthoxml),
roothogs : FastomaOutput.groupPartition(out.resolve('RootHOGs.tsv')),
orthologous_groups: FastomaOutput.groupPartition(out.resolve('OrthologousGroups.tsv')),
ortholog_pairs : FastomaOutput.pairs(out.resolve('orthologs.tsv.gz')),
]
).match() }
)
}
Expand Down
174 changes: 153 additions & 21 deletions nf-tests/default.nf.test.snap
Original file line number Diff line number Diff line change
Expand Up @@ -48,18 +48,6 @@
"species_tree_checked.nwk:md5,4c82a2f2ffa72a595aaf2fd3deddeff7"
],
[
{
"name": "FastOMA_HOGs.orthoxml",
"lineCount": 194
},
{
"name": "OrthologousGroups.tsv",
"lineCount": 28
},
{
"name": "RootHOGs.tsv",
"lineCount": 28
},
{
"name": "AQUAE.fa.hogmap",
"lineCount": 20
Expand All @@ -71,17 +59,161 @@
{
"name": "MYCGE.fa.hogmap",
"lineCount": 14
},
{
"name": "orthologs.tsv.gz",
"lineCount": 18
}
]
],
{
"orthoxml_elements": {
"orthologGroup": 15,
"paralogGroup": 0,
"gene": 28,
"geneRef": 27
},
"hogs": [
"OG[inter1](sp|O66686|TPIS_AQUAE, sp|O84332|TPIS_CHLTR)",
"OG[inter1](sp|O66778|ENO_AQUAE, sp|O84591|ENO_CHLTR)",
"OG[inter1](sp|O67547|SUCD_AQUAE, tr|O84829|O84829_CHLTR)",
"OG[inter1](sp|O67618|LEPA_AQUAE, sp|O84067|LEPA_CHLTR)",
"OG[inter1](sp|O67736|FOLD_AQUAE, sp|O84081|FOLD_CHLTR)",
"OG[inter1](sp|O67943|CH60_AQUAE, sp|P0C0Z7|CH60_CHLTR)",
"OG[inter2](OG[inter1](sp|O66429|EFTU_AQUAE, sp|P0CD71|EFTU_CHLTR), sp|P13927|EFTU_MYCGE)",
"OG[inter2](OG[inter1](sp|O67032|RF1_AQUAE, sp|O84026|RF1_CHLTR), sp|P47500|RF1_MYCGE)",
"OG[inter2](OG[inter1](sp|O67161|G3P_AQUAE, sp|P0CE13|G3P_CHLTR), sp|P47543|G3P_MYCGE)",
"OG[inter2](sp|O66907|ATPA_AQUAE, sp|P47641|ATPA_MYCGE)",
"OG[inter2](sp|O67828|ATPB_AQUAE, sp|P47639|ATPB_MYCGE)",
"OG[inter2](sp|P17821|DNAK_CHLTR, sp|P47547|DNAK_MYCGE)"
],
"roothogs": [
[
"sp|O66429|EFTU_AQUAE",
"sp|P0CD71|EFTU_CHLTR",
"sp|P13927|EFTU_MYCGE"
],
[
"sp|O66686|TPIS_AQUAE",
"sp|O84332|TPIS_CHLTR"
],
[
"sp|O66778|ENO_AQUAE",
"sp|O84591|ENO_CHLTR"
],
[
"sp|O66907|ATPA_AQUAE",
"sp|P47641|ATPA_MYCGE"
],
[
"sp|O67032|RF1_AQUAE",
"sp|O84026|RF1_CHLTR",
"sp|P47500|RF1_MYCGE"
],
[
"sp|O67161|G3P_AQUAE",
"sp|P0CE13|G3P_CHLTR",
"sp|P47543|G3P_MYCGE"
],
[
"sp|O67547|SUCD_AQUAE",
"tr|O84829|O84829_CHLTR"
],
[
"sp|O67618|LEPA_AQUAE",
"sp|O84067|LEPA_CHLTR"
],
[
"sp|O67736|FOLD_AQUAE",
"sp|O84081|FOLD_CHLTR"
],
[
"sp|O67828|ATPB_AQUAE",
"sp|P47639|ATPB_MYCGE"
],
[
"sp|O67943|CH60_AQUAE",
"sp|P0C0Z7|CH60_CHLTR"
],
[
"sp|P17821|DNAK_CHLTR",
"sp|P47547|DNAK_MYCGE"
]
],
"orthologous_groups": [
[
"sp|O66429|EFTU_AQUAE",
"sp|P0CD71|EFTU_CHLTR",
"sp|P13927|EFTU_MYCGE"
],
[
"sp|O66686|TPIS_AQUAE",
"sp|O84332|TPIS_CHLTR"
],
[
"sp|O66778|ENO_AQUAE",
"sp|O84591|ENO_CHLTR"
],
[
"sp|O66907|ATPA_AQUAE",
"sp|P47641|ATPA_MYCGE"
],
[
"sp|O67032|RF1_AQUAE",
"sp|O84026|RF1_CHLTR",
"sp|P47500|RF1_MYCGE"
],
[
"sp|O67161|G3P_AQUAE",
"sp|P0CE13|G3P_CHLTR",
"sp|P47543|G3P_MYCGE"
],
[
"sp|O67547|SUCD_AQUAE",
"tr|O84829|O84829_CHLTR"
],
[
"sp|O67618|LEPA_AQUAE",
"sp|O84067|LEPA_CHLTR"
],
[
"sp|O67736|FOLD_AQUAE",
"sp|O84081|FOLD_CHLTR"
],
[
"sp|O67828|ATPB_AQUAE",
"sp|P47639|ATPB_MYCGE"
],
[
"sp|O67943|CH60_AQUAE",
"sp|P0C0Z7|CH60_CHLTR"
],
[
"sp|P17821|DNAK_CHLTR",
"sp|P47547|DNAK_MYCGE"
]
],
"ortholog_pairs": [
"sp|O66429|EFTU_AQUAE <-> sp|P0CD71|EFTU_CHLTR",
"sp|O66429|EFTU_AQUAE <-> sp|P13927|EFTU_MYCGE",
"sp|O66686|TPIS_AQUAE <-> sp|O84332|TPIS_CHLTR",
"sp|O66778|ENO_AQUAE <-> sp|O84591|ENO_CHLTR",
"sp|O66907|ATPA_AQUAE <-> sp|P47641|ATPA_MYCGE",
"sp|O67032|RF1_AQUAE <-> sp|O84026|RF1_CHLTR",
"sp|O67032|RF1_AQUAE <-> sp|P47500|RF1_MYCGE",
"sp|O67161|G3P_AQUAE <-> sp|P0CE13|G3P_CHLTR",
"sp|O67161|G3P_AQUAE <-> sp|P47543|G3P_MYCGE",
"sp|O67547|SUCD_AQUAE <-> tr|O84829|O84829_CHLTR",
"sp|O67618|LEPA_AQUAE <-> sp|O84067|LEPA_CHLTR",
"sp|O67736|FOLD_AQUAE <-> sp|O84081|FOLD_CHLTR",
"sp|O67828|ATPB_AQUAE <-> sp|P47639|ATPB_MYCGE",
"sp|O67943|CH60_AQUAE <-> sp|P0C0Z7|CH60_CHLTR",
"sp|O84026|RF1_CHLTR <-> sp|P47500|RF1_MYCGE",
"sp|P0CD71|EFTU_CHLTR <-> sp|P13927|EFTU_MYCGE",
"sp|P0CE13|G3P_CHLTR <-> sp|P47543|G3P_MYCGE",
"sp|P17821|DNAK_CHLTR <-> sp|P47547|DNAK_MYCGE"
]
}
],
"timestamp": "2026-10-20T14:21:18.662761",
"meta": {
"nf-test": "0.9.3",
"nextflow": "26.04.6"
},
"timestamp": "2026-08-25T18:07:32.287389"
"nf-test": "0.9.5",
"nextflow": "24.10.2"
}
}
}
Binary file added nf-tests/helpers/input_archive.tar.gz
Binary file not shown.
2 changes: 2 additions & 0 deletions nf-tests/helpers/splice_in/AQUAE.splice
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
sp|O66429|EFTU_AQUAE;sp|O67618|LEPA_AQUAE
sp|O66778|ENO_AQUAE
Loading
Loading