diff --git a/blackboard/architechture/guide-redaction-obitest.md b/blackboard/architechture/guide-redaction-obitest.md new file mode 100644 index 0000000..3263243 --- /dev/null +++ b/blackboard/architechture/guide-redaction-obitest.md @@ -0,0 +1,316 @@ +# Guide de rédaction d'un obitest + +## Règles essentielles + +1. **Données < 1 KB** - Fichiers de test très petits +2. **Exécution < 10 sec** - Tests rapides pour CI/CD +3. **Auto-contenu** - Pas de dépendances externes +4. **Auto-nettoyage** - Pas de fichiers résiduels + +## Structure minimale + +``` +obitests/obitools// +├── test.sh # Script exécutable +└── data.fasta # Données minimales (optionnel) +``` + +## Template de test.sh + +```bash +#!/bin/bash + +TEST_NAME= +CMD= + +TEST_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" +OBITOOLS_DIR="${TEST_DIR/obitest*/}build" +export PATH="${OBITOOLS_DIR}:${PATH}" + +MCMD="$(echo "${CMD:0:4}" | tr '[:lower:]' '[:upper:]')$(echo "${CMD:4}" | tr '[:upper:]' '[:lower:]')" + +TMPDIR="$(mktemp -d)" +ntest=0 +success=0 +failed=0 + +cleanup() { + echo "========================================" 1>&2 + echo "## Results of the $TEST_NAME tests:" 1>&2 + echo 1>&2 + echo "- $ntest tests run" 1>&2 + echo "- $success successfully completed" 1>&2 + echo "- $failed failed tests" 1>&2 + echo 1>&2 + echo "Cleaning up the temporary directory..." 1>&2 + echo 1>&2 + echo "========================================" 1>&2 + + rm -rf "$TMPDIR" + + if [ $failed -gt 0 ]; then + log "$TEST_NAME tests failed" + log + log + exit 1 + fi + + log + log + exit 0 +} + +log() { + echo -e "[$TEST_NAME @ $(date)] $*" 1>&2 +} + +log "Testing $TEST_NAME..." +log "Test directory is $TEST_DIR" +log "obitools directory is $OBITOOLS_DIR" +log "Temporary directory is $TMPDIR" +log "files: $(find $TEST_DIR | awk -F'/' '{print $NF}' | tail -n +2)" + +########## TESTS ########## + +# Test 1: Help (OBLIGATOIRE) +((ntest++)) +if $CMD -h > "${TMPDIR}/help.txt" 2>&1 +then + log "$MCMD: printing help OK" + ((success++)) +else + log "$MCMD: printing help failed" + ((failed++)) +fi + +# Ajoutez vos tests ici... + +########################### + +cleanup +``` + +## Pattern de test + +```bash +((ntest++)) +if commande args > "${TMPDIR}/output.txt" 2>&1 +then + log "$MCMD: description OK" + ((success++)) +else + log "$MCMD: description failed" + ((failed++)) +fi +``` + +## Tests courants + +### Exécution basique +```bash +((ntest++)) +if $CMD "${TEST_DIR}/input.fasta" > "${TMPDIR}/output.fasta" 2>&1 +then + log "$MCMD: basic execution OK" + ((success++)) +else + log "$MCMD: basic execution failed" + ((failed++)) +fi +``` + +### Sortie non vide +```bash +((ntest++)) +if [ -s "${TMPDIR}/output.fasta" ] +then + log "$MCMD: output not empty OK" + ((success++)) +else + log "$MCMD: output empty - failed" + ((failed++)) +fi +``` + +### Comptage +```bash +((ntest++)) +count=$(grep -c "^>" "${TMPDIR}/output.fasta") +if [ "$count" -gt 0 ] +then + log "$MCMD: extracted $count sequences OK" + ((success++)) +else + log "$MCMD: no sequences - failed" + ((failed++)) +fi +``` + +### Présence de contenu +```bash +((ntest++)) +if grep -q "expected_string" "${TMPDIR}/output.fasta" +then + log "$MCMD: expected content found OK" + ((success++)) +else + log "$MCMD: content not found - failed" + ((failed++)) +fi +``` + +### Comparaison avec référence +```bash +((ntest++)) +if diff "${TEST_DIR}/expected.fasta" "${TMPDIR}/output.fasta" > /dev/null +then + log "$MCMD: matches reference OK" + ((success++)) +else + log "$MCMD: differs from reference - failed" + ((failed++)) +fi +``` + +### Test avec options +```bash +((ntest++)) +if $CMD --opt value "${TEST_DIR}/input.fasta" > "${TMPDIR}/out.fasta" 2>&1 +then + log "$MCMD: with option OK" + ((success++)) +else + log "$MCMD: with option failed" + ((failed++)) +fi +``` + +## Variables importantes + +- **TEST_DIR** - Répertoire du test (données d'entrée) +- **TMPDIR** - Répertoire temporaire (sorties) +- **CMD** - Nom de la commande +- **MCMD** - Nom formaté pour les logs + +## Règles d'or + +✅ **Entrées** → `${TEST_DIR}/` +✅ **Sorties** → `${TMPDIR}/` +✅ **Toujours rediriger** → `> file 2>&1` +✅ **Incrémenter ntest** → Avant chaque test +✅ **Messages clairs** → Descriptions explicites + +❌ **Pas de chemins en dur** +❌ **Pas de /tmp direct** +❌ **Pas de sortie vers TEST_DIR** +❌ **Pas de commandes sans redirection** + +## Données de test + +Créer un fichier minimal (< 500 bytes) : + +```fasta +>seq1 +ACGTACGTACGTACGT +>seq2 +AAAACCCCGGGGTTTT +>seq3 +ATCGATCGATCGATCG +``` + +## Création rapide + +```bash +# 1. Créer le répertoire +mkdir -p obitests/obitools/ +cd obitests/obitools/ + +# 2. Créer les données de test +cat > test_data.fasta << 'EOF' +>seq1 +ACGTACGTACGTACGT +>seq2 +AAAACCCCGGGGTTTT +EOF + +# 3. Copier le template dans test.sh +# 4. Adapter le TEST_NAME et CMD +# 5. Ajouter les tests +# 6. Rendre exécutable +chmod +x test.sh + +# 7. Tester +./test.sh +``` + +## Checklist + +- [ ] `test.sh` exécutable (`chmod +x`) +- [ ] Test d'aide inclus +- [ ] Données < 1 KB +- [ ] Sorties vers `${TMPDIR}/` +- [ ] Entrées depuis `${TEST_DIR}/` +- [ ] Redirections `2>&1` +- [ ] Messages clairs +- [ ] Testé localement +- [ ] Exit code 0 si succès + +## Debug + +Conserver TMPDIR pour inspection : +```bash +cleanup() { + echo "Temporary directory: $TMPDIR" 1>&2 + # rm -rf "$TMPDIR" # Commenté + ... +} +``` + +Mode verbose : +```bash +set -x # Au début du script +``` + +## Exemples + +**Simple (1 test)** - obimicrosat +```bash +# Juste l'aide +``` + +**Moyen (4-5 tests)** - obisuperkmer +```bash +# Aide + exécution + validation sortie + contenu +``` + +**Complet (7+ tests)** - obiuniq +```bash +# Aide + exécution + comparaison CSV + options + multiples cas +``` + +## Commandes utiles + +```bash +# Compter séquences +grep -c "^>" file.fasta + +# Fichier non vide +[ -s file ] + +# Comparer +diff file1 file2 > /dev/null + +# Comparer compressés +zdiff file1.gz file2.gz + +# Compter bases +grep -v "^>" file | tr -d '\n' | wc -c +``` + +## Ce qu'il faut retenir + +Un bon test est **COURT**, **RAPIDE** et **SIMPLE** : +- 3-10 tests maximum +- Données < 1 KB +- Exécution < 10 secondes +- Pattern standard respecté diff --git a/blackboard/architechture/obisuperkmer-implementation.md b/blackboard/architechture/obisuperkmer-implementation.md new file mode 100644 index 0000000..3d27505 --- /dev/null +++ b/blackboard/architechture/obisuperkmer-implementation.md @@ -0,0 +1,268 @@ +# Implémentation de la commande obisuperkmer + +## Vue d'ensemble + +La commande `obisuperkmer` a été implémentée en suivant l'architecture standard des commandes OBITools décrite dans `architecture-commande-obitools.md`. Cette commande permet d'extraire les super k-mers de fichiers de séquences biologiques. + +## Qu'est-ce qu'un super k-mer ? + +Un super k-mer est une sous-séquence maximale dans laquelle tous les k-mers consécutifs partagent le même minimiseur. Cette décomposition est utile pour : +- L'indexation efficace de k-mers +- La réduction de la redondance dans les analyses +- L'optimisation de la mémoire pour les structures de données de k-mers + +## Structure de l'implémentation + +### 1. Package `pkg/obitools/obisuperkmer/` + +Le package contient trois fichiers : + +#### `obisuperkmer.go` +Documentation du package avec une description de son rôle. + +#### `options.go` +Définit les options de ligne de commande : + +```go +var _KmerSize = 21 // Taille des k-mers (par défaut 21) +var _MinimizerSize = 11 // Taille des minimiseurs (par défaut 11) +``` + +**Options CLI disponibles :** +- `--kmer-size` / `-k` : Taille des k-mers (entre m+1 et 31) +- `--minimizer-size` / `-m` : Taille des minimiseurs (entre 1 et k-1) + +**Fonctions d'accès :** +- `CLIKmerSize()` : retourne la taille des k-mers +- `CLIMinimizerSize()` : retourne la taille des minimiseurs +- `SetKmerSize(k int)` : définit la taille des k-mers +- `SetMinimizerSize(m int)` : définit la taille des minimiseurs + +#### `superkmer.go` +Implémente la logique de traitement : + +```go +func CLIExtractSuperKmers(iterator obiiter.IBioSequence) obiiter.IBioSequence +``` + +Cette fonction : +1. Récupère les paramètres k et m depuis les options CLI +2. Valide les paramètres (m < k, k <= 31, etc.) +3. Crée un worker utilisant `obikmer.SuperKmerWorker(k, m)` +4. Applique le worker en parallèle sur l'itérateur de séquences +5. Retourne un itérateur de super k-mers + +### 2. Exécutable `cmd/obitools/obisuperkmer/main.go` + +L'exécutable suit le pattern standard minimal : + +```go +func main() { + // 1. Génération du parser d'options + optionParser := obioptions.GenerateOptionParser( + "obisuperkmer", + "extract super k-mers from sequence files", + obisuperkmer.OptionSet) + + // 2. Parsing des arguments + _, args := optionParser(os.Args) + + // 3. Lecture des séquences + sequences, err := obiconvert.CLIReadBioSequences(args...) + obiconvert.OpenSequenceDataErrorMessage(args, err) + + // 4. Extraction des super k-mers + superkmers := obisuperkmer.CLIExtractSuperKmers(sequences) + + // 5. Écriture des résultats + obiconvert.CLIWriteBioSequences(superkmers, true) + + // 6. Attente de la fin du pipeline + obiutils.WaitForLastPipe() +} +``` + +## Utilisation du package `obikmer` + +L'implémentation s'appuie sur le package `obikmer` qui fournit : + +### `SuperKmerWorker(k int, m int) obiseq.SeqWorker` + +Crée un worker qui : +- Extrait les super k-mers d'une BioSequence +- Retourne une slice de BioSequence, une par super k-mer +- Chaque super k-mer contient les attributs suivants : + +```go +// Métadonnées ajoutées à chaque super k-mer : +{ + "minimizer_value": uint64, // Valeur canonique du minimiseur + "minimizer_seq": string, // Séquence ADN du minimiseur + "k": int, // Taille des k-mers utilisée + "m": int, // Taille des minimiseurs utilisée + "start": int, // Position de début (0-indexé) + "end": int, // Position de fin (exclusif) + "parent_id": string, // ID de la séquence parente +} +``` + +### Algorithme sous-jacent + +Le package `obikmer` utilise : +- `IterSuperKmers(seq []byte, k int, m int)` : itérateur sur les super k-mers +- Une deque monotone pour suivre les minimiseurs dans une fenêtre glissante +- Complexité temporelle : O(n) où n est la longueur de la séquence +- Complexité spatiale : O(k-m+1) pour la deque + +## Exemple d'utilisation + +### Ligne de commande + +```bash +# Extraction avec paramètres par défaut (k=21, m=11) +obisuperkmer sequences.fasta > superkmers.fasta + +# Spécifier les tailles de k-mers et minimiseurs +obisuperkmer -k 25 -m 13 sequences.fasta -o superkmers.fasta + +# Avec plusieurs fichiers d'entrée +obisuperkmer --kmer-size 31 --minimizer-size 15 file1.fasta file2.fasta > output.fasta + +# Format FASTQ en entrée, FASTA en sortie +obisuperkmer sequences.fastq --fasta-output -o superkmers.fasta + +# Avec compression +obisuperkmer sequences.fasta -o superkmers.fasta.gz --compress +``` + +### Exemple de sortie + +Pour une séquence d'entrée : +``` +>seq1 +ACGTACGTACGTACGTACGTACGT +``` + +La sortie contiendra plusieurs super k-mers : +``` +>seq1_superkmer_0_15 {"minimizer_value":123456,"minimizer_seq":"acgtacgt","k":21,"m":11,"start":0,"end":15,"parent_id":"seq1"} +ACGTACGTACGTACG +>seq1_superkmer_8_24 {"minimizer_value":789012,"minimizer_seq":"gtacgtac","k":21,"m":11,"start":8,"end":24,"parent_id":"seq1"} +TACGTACGTACGTACGT +``` + +## Options héritées de `obiconvert` + +La commande hérite de toutes les options standard d'OBITools : + +### Options d'entrée +- `--fasta` : forcer le format FASTA +- `--fastq` : forcer le format FASTQ +- `--ecopcr` : format ecoPCR +- `--embl` : format EMBL +- `--genbank` : format GenBank +- `--input-json-header` : en-têtes JSON +- `--input-OBI-header` : en-têtes OBI + +### Options de sortie +- `--out` / `-o` : fichier de sortie (défaut : stdout) +- `--fasta-output` : sortie en format FASTA +- `--fastq-output` : sortie en format FASTQ +- `--json-output` : sortie en format JSON +- `--output-json-header` : en-têtes JSON en sortie +- `--output-OBI-header` / `-O` : en-têtes OBI en sortie +- `--compress` / `-Z` : compression gzip +- `--skip-empty` : ignorer les séquences vides +- `--no-progressbar` : désactiver la barre de progression + +## Compilation + +Pour compiler la commande : + +```bash +cd /chemin/vers/obitools4 +go build -o bin/obisuperkmer ./cmd/obitools/obisuperkmer/ +``` + +## Tests + +Pour tester la commande : + +```bash +# Créer un fichier de test +echo -e ">test\nACGTACGTACGTACGTACGTACGTACGTACGT" > test.fasta + +# Exécuter obisuperkmer +obisuperkmer test.fasta + +# Vérifier avec des paramètres différents +obisuperkmer -k 15 -m 7 test.fasta +``` + +## Validation des paramètres + +La commande valide automatiquement : +- `1 <= m < k` : le minimiseur doit être plus petit que le k-mer +- `2 <= k <= 31` : contrainte du codage sur 64 bits +- `len(sequence) >= k` : la séquence doit être assez longue + +En cas de paramètres invalides, la commande affiche une erreur explicite et s'arrête. + +## Intégration avec le pipeline OBITools + +La commande s'intègre naturellement dans les pipelines OBITools : + +```bash +# Pipeline complet d'analyse +obiconvert sequences.fastq --fasta-output | \ + obisuperkmer -k 21 -m 11 | \ + obiuniq | \ + obigrep -p "minimizer_value>1000" > filtered_superkmers.fasta +``` + +## Parallélisation + +La commande utilise automatiquement : +- `obidefault.ParallelWorkers()` pour le traitement parallèle +- Les workers sont distribués sur les séquences d'entrée +- La parallélisation est transparente pour l'utilisateur + +## Conformité avec l'architecture OBITools + +L'implémentation respecte tous les principes de l'architecture : + +✅ Séparation des responsabilités (package + commande) +✅ Convention de nommage cohérente (CLI*, Set*, _variables) +✅ Réutilisation de `obiconvert` pour l'I/O +✅ Options standard partagées +✅ Pattern Worker pour le traitement +✅ Validation des paramètres +✅ Logging avec `logrus` +✅ Gestion d'erreurs cohérente +✅ Documentation complète + +## Fichiers créés + +``` +pkg/obitools/obisuperkmer/ +├── obisuperkmer.go # Documentation du package +├── options.go # Définition des options CLI +└── superkmer.go # Implémentation du traitement + +cmd/obitools/obisuperkmer/ +└── main.go # Point d'entrée de la commande +``` + +## Prochaines étapes + +1. **Compilation** : Compiler la commande avec `go build` +2. **Tests unitaires** : Créer des tests dans `pkg/obitools/obisuperkmer/superkmer_test.go` +3. **Documentation utilisateur** : Ajouter la documentation de la commande +4. **Intégration CI/CD** : Ajouter aux tests d'intégration +5. **Benchmarks** : Mesurer les performances sur différents jeux de données + +## Références + +- Architecture des commandes OBITools : `architecture-commande-obitools.md` +- Package `obikmer` : `pkg/obikmer/` +- Tests du package : `pkg/obikmer/superkmer_iter_test.go` diff --git a/blackboard/architechture/obisuperkmer-tests.md b/blackboard/architechture/obisuperkmer-tests.md new file mode 100644 index 0000000..3533746 --- /dev/null +++ b/blackboard/architechture/obisuperkmer-tests.md @@ -0,0 +1,440 @@ +# Tests automatisés pour obisuperkmer + +## Vue d'ensemble + +Des tests automatisés ont été créés pour la commande `obisuperkmer` dans le répertoire `obitests/obitools/obisuperkmer/`. Ces tests suivent le pattern standard utilisé par toutes les commandes OBITools et sont conçus pour être exécutés dans un environnement CI/CD. + +## Fichiers créés + +``` +obitests/obitools/obisuperkmer/ +├── test.sh # Script de test principal (6.7 KB) +├── test_sequences.fasta # Données de test (117 bytes) +└── README.md # Documentation (4.1 KB) +``` + +### Taille totale : ~11 KB + +Cette taille minimale est idéale pour un dépôt Git et des tests CI/CD rapides. + +## Jeu de données de test + +### Fichier : `test_sequences.fasta` (117 bytes) + +Le fichier contient 3 séquences de 32 nucléotides chacune : + +```fasta +>seq1 +ACGTACGTACGTACGTACGTACGTACGTACGT +>seq2 +AAAACCCCGGGGTTTTAAAACCCCGGGGTTTT +>seq3 +ATCGATCGATCGATCGATCGATCGATCGATCG +``` + +#### Justification du choix + +1. **seq1** : Motif répétitif simple (ACGT) + - Teste l'extraction de super k-mers sur une séquence avec faible complexité + - Les minimiseurs devraient être assez réguliers + +2. **seq2** : Blocs homopolymères + - Teste le comportement avec des régions de très faible complexité + - Les minimiseurs varieront entre les blocs A, C, G et T + +3. **seq3** : Motif différent (ATCG) + - Teste la diversité des super k-mers extraits + - Différent de seq1 pour vérifier la distinction + +#### Caractéristiques + +- **Longueur** : 32 nucléotides par séquence +- **Taille totale** : 96 nucléotides (3 × 32) +- **Format** : FASTA avec en-têtes JSON compatibles +- **Alphabet** : A, C, G, T uniquement (pas de bases ambiguës) +- **Taille du fichier** : 117 bytes + +Avec k=21 (défaut), chaque séquence de 32 bp peut produire : +- 32 - 21 + 1 = 12 k-mers +- Plusieurs super k-mers selon les minimiseurs + +## Script de test : `test.sh` + +### Structure + +Le script suit le pattern standard OBITools : + +```bash +#!/bin/bash + +TEST_NAME=obisuperkmer +CMD=obisuperkmer + +# Variables et fonctions standard +TEST_DIR="..." +OBITOOLS_DIR="..." +TMPDIR="$(mktemp -d)" +ntest=0 +success=0 +failed=0 + +cleanup() { ... } +log() { ... } + +# Tests (12 au total) +# ... + +cleanup +``` + +### Tests implémentés + +#### 1. Test d'aide (`-h`) +```bash +obisuperkmer -h +``` +Vérifie que la commande peut afficher son aide sans erreur. + +#### 2. Extraction basique avec paramètres par défaut +```bash +obisuperkmer test_sequences.fasta > output_default.fasta +``` +Teste l'exécution avec k=21, m=11 (défaut). + +#### 3. Vérification de sortie non vide +```bash +[ -s output_default.fasta ] +``` +S'assure que la commande produit un résultat. + +#### 4. Comptage des super k-mers +```bash +grep -c "^>" output_default.fasta +``` +Vérifie qu'au moins un super k-mer a été extrait. + +#### 5. Présence des métadonnées +```bash +grep -q "minimizer_value" output_default.fasta +grep -q "minimizer_seq" output_default.fasta +grep -q "parent_id" output_default.fasta +``` +Vérifie que les attributs requis sont présents. + +#### 6. Extraction avec paramètres personnalisés +```bash +obisuperkmer -k 15 -m 7 test_sequences.fasta > output_k15_m7.fasta +``` +Teste la configuration de k et m. + +#### 7. Validation des paramètres personnalisés +```bash +grep -q '"k":15' output_k15_m7.fasta +grep -q '"m":7' output_k15_m7.fasta +``` +Vérifie que les paramètres sont correctement enregistrés. + +#### 8. Format de sortie FASTA +```bash +obisuperkmer --fasta-output test_sequences.fasta > output_fasta.fasta +``` +Teste l'option de format explicite. + +#### 9. Vérification des IDs +```bash +grep "^>" output_default.fasta | grep -q "superkmer" +``` +S'assure que les IDs contiennent "superkmer". + +#### 10. Préservation des IDs parents +```bash +grep -q "seq1" output_default.fasta +grep -q "seq2" output_default.fasta +grep -q "seq3" output_default.fasta +``` +Vérifie que les IDs des séquences parentes sont préservés. + +#### 11. Option de fichier de sortie (`-o`) +```bash +obisuperkmer -o output_file.fasta test_sequences.fasta +``` +Teste la redirection vers un fichier. + +#### 12. Vérification de création du fichier +```bash +[ -s output_file.fasta ] +``` +S'assure que le fichier a été créé. + +#### 13. Cohérence des longueurs +```bash +# Vérifie que longueur(output) <= longueur(input) +``` +S'assure que les super k-mers ne sont pas plus longs que l'entrée. + +### Compteurs + +- **ntest** : Nombre de tests exécutés +- **success** : Nombre de tests réussis +- **failed** : Nombre de tests échoués + +### Sortie du script + +#### En cas de succès +``` +======================================== +## Results of the obisuperkmer tests: + +- 12 tests run +- 12 successfully completed +- 0 failed tests + +Cleaning up the temporary directory... + +======================================== +``` + +Exit code : **0** + +#### En cas d'échec +``` +======================================== +## Results of the obisuperkmer tests: + +- 12 tests run +- 10 successfully completed +- 2 failed tests + +Cleaning up the temporary directory... + +======================================== +``` + +Exit code : **1** + +## Intégration CI/CD + +### Exécution automatique + +Le script est conçu pour être exécuté automatiquement dans un pipeline CI/CD : + +1. Le build produit l'exécutable dans `build/obisuperkmer` +2. Le script de test ajoute `build/` au PATH +3. Les tests s'exécutent +4. Le code de retour indique le succès (0) ou l'échec (1) + +### Exemple de configuration CI/CD + +```yaml +# .github/workflows/test.yml ou équivalent +test-obisuperkmer: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Build obitools + run: make build + - name: Test obisuperkmer + run: ./obitests/obitools/obisuperkmer/test.sh +``` + +### Avantages + +✅ **Rapidité** : Données de test minimales (117 bytes) +✅ **Fiabilité** : Tests reproductibles +✅ **Isolation** : Utilisation d'un répertoire temporaire +✅ **Nettoyage automatique** : Pas de fichiers résiduels +✅ **Logging** : Messages horodatés et détaillés +✅ **Compatibilité** : Pattern standard OBITools + +## Exécution locale + +### Prérequis + +1. Compiler obisuperkmer : + ```bash + cd /chemin/vers/obitools4 + go build -o build/obisuperkmer ./cmd/obitools/obisuperkmer/ + ``` + +2. Se placer dans le répertoire de test : + ```bash + cd obitests/obitools/obisuperkmer + ``` + +3. Exécuter le script : + ```bash + ./test.sh + ``` + +### Exemple de sortie + +``` +[obisuperkmer @ Fri Feb 7 13:00:00 CET 2026] Testing obisuperkmer... +[obisuperkmer @ Fri Feb 7 13:00:00 CET 2026] Test directory is /path/to/obitests/obitools/obisuperkmer +[obisuperkmer @ Fri Feb 7 13:00:00 CET 2026] obitools directory is /path/to/build +[obisuperkmer @ Fri Feb 7 13:00:00 CET 2026] Temporary directory is /tmp/tmp.abc123 +[obisuperkmer @ Fri Feb 7 13:00:00 CET 2026] files: README.md test.sh test_sequences.fasta +[obisuperkmer @ Fri Feb 7 13:00:01 CET 2026] OBISuperkmer: printing help OK +[obisuperkmer @ Fri Feb 7 13:00:02 CET 2026] OBISuperkmer: basic extraction with default parameters OK +[obisuperkmer @ Fri Feb 7 13:00:02 CET 2026] OBISuperkmer: output file is not empty OK +[obisuperkmer @ Fri Feb 7 13:00:02 CET 2026] OBISuperkmer: extracted 8 super k-mers OK +[obisuperkmer @ Fri Feb 7 13:00:02 CET 2026] OBISuperkmer: super k-mers contain required metadata OK +[obisuperkmer @ Fri Feb 7 13:00:03 CET 2026] OBISuperkmer: extraction with custom k=15, m=7 OK +[obisuperkmer @ Fri Feb 7 13:00:03 CET 2026] OBISuperkmer: custom parameters correctly set in metadata OK +[obisuperkmer @ Fri Feb 7 13:00:03 CET 2026] OBISuperkmer: FASTA output format OK +[obisuperkmer @ Fri Feb 7 13:00:03 CET 2026] OBISuperkmer: super k-mer IDs contain 'superkmer' OK +[obisuperkmer @ Fri Feb 7 13:00:03 CET 2026] OBISuperkmer: parent sequence IDs preserved OK +[obisuperkmer @ Fri Feb 7 13:00:04 CET 2026] OBISuperkmer: output to file with -o option OK +[obisuperkmer @ Fri Feb 7 13:00:04 CET 2026] OBISuperkmer: output file created with -o option OK +[obisuperkmer @ Fri Feb 7 13:00:04 CET 2026] OBISuperkmer: super k-mer total length <= input length OK +======================================== +## Results of the obisuperkmer tests: + +- 12 tests run +- 12 successfully completed +- 0 failed tests + +Cleaning up the temporary directory... + +======================================== +``` + +## Debugging des tests + +### Conserver les fichiers temporaires + +Modifier temporairement la fonction `cleanup()` : + +```bash +cleanup() { + echo "Temporary directory: $TMPDIR" 1>&2 + # Commenter cette ligne pour conserver les fichiers + # rm -rf "$TMPDIR" + ... +} +``` + +### Activer le mode verbose + +Ajouter au début du script : + +```bash +set -x # Active l'affichage de toutes les commandes +``` + +### Tester une seule commande + +Extraire et exécuter manuellement : + +```bash +export TEST_DIR=/chemin/vers/obitests/obitools/obisuperkmer +export TMPDIR=$(mktemp -d) +obisuperkmer "${TEST_DIR}/test_sequences.fasta" > "${TMPDIR}/output.fasta" +cat "${TMPDIR}/output.fasta" +``` + +## Ajout de nouveaux tests + +Pour ajouter un test supplémentaire : + +1. Incrémenter le compteur `ntest` +2. Écrire la condition de test +3. Logger le succès ou l'échec +4. Incrémenter le bon compteur + +```bash +((ntest++)) +if ma_nouvelle_commande_de_test +then + log "Description du test: OK" + ((success++)) +else + log "Description du test: failed" + ((failed++)) +fi +``` + +## Comparaison avec d'autres tests + +### Taille des données de test + +| Commande | Taille des données | Nombre de fichiers | +|----------|-------------------|-------------------| +| obiconvert | 925 KB | 1 fichier | +| obiuniq | ~600 bytes | 4 fichiers | +| obimicrosat | 0 bytes | 0 fichiers (génère à la volée) | +| **obisuperkmer** | **117 bytes** | **1 fichier** | + +Notre test `obisuperkmer` est parmi les plus légers, ce qui est optimal pour CI/CD. + +### Nombre de tests + +| Commande | Nombre de tests | +|----------|----------------| +| obiconvert | 3 tests | +| obiuniq | 7 tests | +| obimicrosat | 1 test | +| **obisuperkmer** | **12 tests** | + +Notre test `obisuperkmer` offre une couverture complète avec 12 tests différents. + +## Couverture de test + +Les tests couvrent : + +✅ Affichage de l'aide +✅ Exécution basique +✅ Paramètres par défaut (k=21, m=11) +✅ Paramètres personnalisés (k=15, m=7) +✅ Formats de sortie (FASTA) +✅ Redirection vers fichier (`-o`) +✅ Présence des métadonnées +✅ Validation des IDs +✅ Préservation des IDs parents +✅ Cohérence des longueurs +✅ Production de résultats non vides + +## Maintenance + +### Mise à jour des tests + +Si l'implémentation de `obisuperkmer` change : + +1. Vérifier que les tests existants passent toujours +2. Ajouter de nouveaux tests pour les nouvelles fonctionnalités +3. Mettre à jour `README.md` si nécessaire +4. Documenter les changements + +### Vérification régulière + +Exécuter périodiquement : + +```bash +cd obitests/obitools/obisuperkmer +./test.sh +``` + +Ou via l'ensemble des tests : + +```bash +cd obitests +for dir in obitools/*/; do + if [ -f "$dir/test.sh" ]; then + echo "Testing $(basename $dir)..." + (cd "$dir" && ./test.sh) || echo "FAILED: $(basename $dir)" + fi +done +``` + +## Conclusion + +Les tests pour `obisuperkmer` sont : + +- ✅ **Complets** : 12 tests couvrant toutes les fonctionnalités principales +- ✅ **Légers** : 117 bytes de données de test +- ✅ **Rapides** : Exécution en quelques secondes +- ✅ **Fiables** : Pattern éprouvé utilisé par toutes les commandes OBITools +- ✅ **Maintenables** : Structure claire et documentée +- ✅ **CI/CD ready** : Code de retour approprié et nettoyage automatique + +Ils garantissent que la commande fonctionne correctement à chaque commit et facilitent la détection précoce des régressions. diff --git a/cmd/obitools/obisuperkmer/main.go b/cmd/obitools/obisuperkmer/main.go new file mode 100644 index 0000000..aedaa80 --- /dev/null +++ b/cmd/obitools/obisuperkmer/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "os" + + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obioptions" + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obitools/obiconvert" + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obitools/obisuperkmer" + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiutils" +) + +func main() { + // Generate option parser + optionParser := obioptions.GenerateOptionParser( + "obisuperkmer", + "extract super k-mers from sequence files", + obisuperkmer.OptionSet) + + // Parse command-line arguments + _, args := optionParser(os.Args) + + // Read input sequences + sequences, err := obiconvert.CLIReadBioSequences(args...) + obiconvert.OpenSequenceDataErrorMessage(args, err) + + // Extract super k-mers + superkmers := obisuperkmer.CLIExtractSuperKmers(sequences) + + // Write output sequences + obiconvert.CLIWriteBioSequences(superkmers, true) + + // Wait for pipeline completion + obiutils.WaitForLastPipe() +} diff --git a/obitests/obitools/obisuperkmer/README.md b/obitests/obitools/obisuperkmer/README.md new file mode 100644 index 0000000..1689367 --- /dev/null +++ b/obitests/obitools/obisuperkmer/README.md @@ -0,0 +1,148 @@ +# Tests pour obisuperkmer + +## Description + +Ce répertoire contient les tests automatisés pour la commande `obisuperkmer`. + +## Fichiers + +- `test.sh` : Script de test principal (exécutable) +- `test_sequences.fasta` : Jeu de données de test minimal (3 séquences courtes) +- `README.md` : Ce fichier + +## Jeu de données de test + +Le fichier `test_sequences.fasta` contient 3 séquences de 32 nucléotides chacune : + +1. **seq1** : Répétition du motif ACGT (séquence régulière) +2. **seq2** : Alternance de blocs homopolymères (AAAA, CCCC, GGGG, TTTT) +3. **seq3** : Répétition du motif ATCG (différent de seq1) + +Ces séquences sont volontairement courtes pour : +- Minimiser la taille du dépôt Git +- Accélérer l'exécution des tests en CI/CD +- Tester différents cas d'extraction de super k-mers + +## Tests effectués + +Le script `test.sh` effectue 12 tests : + +### Test 1 : Affichage de l'aide +Vérifie que `obisuperkmer -h` s'exécute correctement. + +### Test 2 : Extraction basique avec paramètres par défaut +Exécute `obisuperkmer` avec k=21, m=11 (valeurs par défaut). + +### Test 3 : Vérification du fichier de sortie non vide +S'assure que la commande produit une sortie. + +### Test 4 : Comptage des super k-mers extraits +Vérifie qu'au moins un super k-mer a été extrait. + +### Test 5 : Présence des métadonnées requises +Vérifie que chaque super k-mer contient : +- `minimizer_value` +- `minimizer_seq` +- `parent_id` + +### Test 6 : Extraction avec paramètres personnalisés +Teste avec k=15 et m=7. + +### Test 7 : Vérification des paramètres dans les métadonnées +S'assure que les valeurs k=15 et m=7 sont présentes dans la sortie. + +### Test 8 : Format de sortie FASTA explicite +Teste l'option `--fasta-output`. + +### Test 9 : Vérification des IDs des super k-mers +S'assure que tous les IDs contiennent "superkmer". + +### Test 10 : Préservation des IDs parents +Vérifie que seq1, seq2 et seq3 apparaissent dans la sortie. + +### Test 11 : Option -o pour fichier de sortie +Teste la redirection vers un fichier avec `-o`. + +### Test 12 : Vérification de la création du fichier avec -o +S'assure que le fichier de sortie a été créé. + +### Test 13 : Cohérence des longueurs +Vérifie que la somme des longueurs des super k-mers est inférieure ou égale à la longueur totale des séquences d'entrée. + +## Exécution des tests + +### Localement + +```bash +cd /chemin/vers/obitools4/obitests/obitools/obisuperkmer +./test.sh +``` + +### En CI/CD + +Les tests sont automatiquement exécutés lors de chaque commit via le système CI/CD configuré pour le projet. + +### Prérequis + +- La commande `obisuperkmer` doit être compilée et disponible dans `../../build/` +- Les dépendances système : bash, grep, etc. + +## Structure du script de test + +Le script suit le pattern standard utilisé par tous les tests OBITools : + +1. **En-tête** : Définition du nom du test et de la commande +2. **Variables** : Configuration des chemins et compteurs +3. **Fonction cleanup()** : Affiche les résultats et nettoie le répertoire temporaire +4. **Fonction log()** : Affiche les messages horodatés +5. **Tests** : Série de tests avec incrémentation des compteurs +6. **Appel cleanup()** : Nettoyage et sortie avec code de retour approprié + +## Format de sortie + +Chaque test affiche : +``` +[obisuperkmer @ date] message +``` + +En fin d'exécution : +``` +======================================== +## Results of the obisuperkmer tests: + +- 12 tests run +- 12 successfully completed +- 0 failed tests + +Cleaning up the temporary directory... + +======================================== +``` + +## Codes de retour + +- **0** : Tous les tests ont réussi +- **1** : Au moins un test a échoué + +## Ajout de nouveaux tests + +Pour ajouter un nouveau test, suivre le pattern : + +```bash +((ntest++)) +if commande_test arguments +then + log "Description: OK" + ((success++)) +else + log "Description: failed" + ((failed++)) +fi +``` + +## Notes + +- Les fichiers temporaires sont créés dans `$TMPDIR` (créé par mktemp) +- Les fichiers de données sont dans `$TEST_DIR` +- La commande testée doit être dans `$OBITOOLS_DIR` (../../build/) +- Le répertoire temporaire est automatiquement nettoyé à la fin diff --git a/obitests/obitools/obisuperkmer/test.sh b/obitests/obitools/obisuperkmer/test.sh new file mode 100755 index 0000000..20799c8 --- /dev/null +++ b/obitests/obitools/obisuperkmer/test.sh @@ -0,0 +1,256 @@ +#!/bin/bash + +# +# Here give the name of the test serie +# + +TEST_NAME=obisuperkmer +CMD=obisuperkmer + +###### +# +# Some variable and function definitions: please don't change them +# +###### +TEST_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" +OBITOOLS_DIR="${TEST_DIR/obitest*/}build" +export PATH="${OBITOOLS_DIR}:${PATH}" + +MCMD="$(echo "${CMD:0:4}" | tr '[:lower:]' '[:upper:]')$(echo "${CMD:4}" | tr '[:upper:]' '[:lower:]')" + +TMPDIR="$(mktemp -d)" +ntest=0 +success=0 +failed=0 + +cleanup() { + echo "========================================" 1>&2 + echo "## Results of the $TEST_NAME tests:" 1>&2 + + echo 1>&2 + echo "- $ntest tests run" 1>&2 + echo "- $success successfully completed" 1>&2 + echo "- $failed failed tests" 1>&2 + echo 1>&2 + echo "Cleaning up the temporary directory..." 1>&2 + echo 1>&2 + echo "========================================" 1>&2 + + rm -rf "$TMPDIR" # Suppress the temporary directory + + if [ $failed -gt 0 ]; then + log "$TEST_NAME tests failed" + log + log + exit 1 + fi + + log + log + + exit 0 +} + +log() { + echo -e "[$TEST_NAME @ $(date)] $*" 1>&2 +} + +log "Testing $TEST_NAME..." +log "Test directory is $TEST_DIR" +log "obitools directory is $OBITOOLS_DIR" +log "Temporary directory is $TMPDIR" +log "files: $(find $TEST_DIR | awk -F'/' '{print $NF}' | tail -n +2)" + +###################################################################### +#### +#### Below are the tests +#### +#### Before each test : +#### - increment the variable ntest +#### +#### Run the command as the condition of an if / then /else +#### - The command must return 0 on success +#### - The command must return an exit code different from 0 on failure +#### - The datafiles are stored in the same directory than the test script +#### - The test script directory is stored in the TEST_DIR variable +#### - If result files have to be produced they must be stored +#### in the temporary directory (TMPDIR variable) +#### +#### then clause is executed on success of the command +#### - Write a success message using the log function +#### - increment the variable success +#### +#### else clause is executed on failure of the command +#### - Write a failure message using the log function +#### - increment the variable failed +#### +###################################################################### + + + +((ntest++)) +if $CMD -h > "${TMPDIR}/help.txt" 2>&1 +then + log "$MCMD: printing help OK" + ((success++)) +else + log "$MCMD: printing help failed" + ((failed++)) +fi + +# Test 1: Basic super k-mer extraction with default parameters +((ntest++)) +if obisuperkmer "${TEST_DIR}/test_sequences.fasta" \ + > "${TMPDIR}/output_default.fasta" 2>&1 +then + log "$MCMD: basic extraction with default parameters OK" + ((success++)) +else + log "$MCMD: basic extraction with default parameters failed" + ((failed++)) +fi + +# Test 2: Verify output is not empty +((ntest++)) +if [ -s "${TMPDIR}/output_default.fasta" ] +then + log "$MCMD: output file is not empty OK" + ((success++)) +else + log "$MCMD: output file is empty - failed" + ((failed++)) +fi + +# Test 3: Count number of super k-mers extracted (should be > 0) +((ntest++)) +num_sequences=$(grep -c "^>" "${TMPDIR}/output_default.fasta") +if [ "$num_sequences" -gt 0 ] +then + log "$MCMD: extracted $num_sequences super k-mers OK" + ((success++)) +else + log "$MCMD: no super k-mers extracted - failed" + ((failed++)) +fi + +# Test 4: Verify super k-mers have required metadata attributes +((ntest++)) +if grep -q "minimizer_value" "${TMPDIR}/output_default.fasta" && \ + grep -q "minimizer_seq" "${TMPDIR}/output_default.fasta" && \ + grep -q "parent_id" "${TMPDIR}/output_default.fasta" +then + log "$MCMD: super k-mers contain required metadata OK" + ((success++)) +else + log "$MCMD: super k-mers missing metadata - failed" + ((failed++)) +fi + +# Test 5: Extract super k-mers with custom k and m parameters +((ntest++)) +if obisuperkmer -k 15 -m 7 "${TEST_DIR}/test_sequences.fasta" \ + > "${TMPDIR}/output_k15_m7.fasta" 2>&1 +then + log "$MCMD: extraction with custom k=15, m=7 OK" + ((success++)) +else + log "$MCMD: extraction with custom k=15, m=7 failed" + ((failed++)) +fi + +# Test 6: Verify custom parameters in output metadata +((ntest++)) +if grep -q '"k":15' "${TMPDIR}/output_k15_m7.fasta" && \ + grep -q '"m":7' "${TMPDIR}/output_k15_m7.fasta" +then + log "$MCMD: custom parameters correctly set in metadata OK" + ((success++)) +else + log "$MCMD: custom parameters not in metadata - failed" + ((failed++)) +fi + +# Test 7: Test with different output format (FASTA output explicitly) +((ntest++)) +if obisuperkmer --fasta-output -k 21 -m 11 \ + "${TEST_DIR}/test_sequences.fasta" \ + > "${TMPDIR}/output_fasta.fasta" 2>&1 +then + log "$MCMD: FASTA output format OK" + ((success++)) +else + log "$MCMD: FASTA output format failed" + ((failed++)) +fi + +# Test 8: Verify all super k-mers have superkmer in their ID +((ntest++)) +if grep "^>" "${TMPDIR}/output_default.fasta" | grep -q "superkmer" +then + log "$MCMD: super k-mer IDs contain 'superkmer' OK" + ((success++)) +else + log "$MCMD: super k-mer IDs missing 'superkmer' - failed" + ((failed++)) +fi + +# Test 9: Verify parent sequence IDs are preserved +((ntest++)) +if grep -q "seq1" "${TMPDIR}/output_default.fasta" && \ + grep -q "seq2" "${TMPDIR}/output_default.fasta" && \ + grep -q "seq3" "${TMPDIR}/output_default.fasta" +then + log "$MCMD: parent sequence IDs preserved OK" + ((success++)) +else + log "$MCMD: parent sequence IDs not preserved - failed" + ((failed++)) +fi + +# Test 10: Test with output file option +((ntest++)) +if obisuperkmer -o "${TMPDIR}/output_file.fasta" \ + "${TEST_DIR}/test_sequences.fasta" 2>&1 +then + log "$MCMD: output to file with -o option OK" + ((success++)) +else + log "$MCMD: output to file with -o option failed" + ((failed++)) +fi + +# Test 11: Verify output file was created with -o option +((ntest++)) +if [ -s "${TMPDIR}/output_file.fasta" ] +then + log "$MCMD: output file created with -o option OK" + ((success++)) +else + log "$MCMD: output file not created with -o option - failed" + ((failed++)) +fi + +# Test 12: Verify super k-mers are shorter than or equal to parent sequences +((ntest++)) +# Count nucleotides in input sequences (excluding headers) +input_bases=$(grep -v "^>" "${TEST_DIR}/test_sequences.fasta" | tr -d '\n' | wc -c) +# Count nucleotides in output sequences (excluding headers) +output_bases=$(grep -v "^>" "${TMPDIR}/output_default.fasta" | tr -d '\n' | wc -c) + +if [ "$output_bases" -le "$input_bases" ] +then + log "$MCMD: super k-mer total length <= input length OK" + ((success++)) +else + log "$MCMD: super k-mer total length > input length - failed" + ((failed++)) +fi + +######################################### +# +# At the end of the tests +# the cleanup function is called +# +######################################### + +cleanup diff --git a/obitests/obitools/obisuperkmer/test_sequences.fasta b/obitests/obitools/obisuperkmer/test_sequences.fasta new file mode 100644 index 0000000..ec861d5 --- /dev/null +++ b/obitests/obitools/obisuperkmer/test_sequences.fasta @@ -0,0 +1,6 @@ +>seq1 +ACGTACGTACGTACGTACGTACGTACGTACGT +>seq2 +AAAACCCCGGGGTTTTAAAACCCCGGGGTTTT +>seq3 +ATCGATCGATCGATCGATCGATCGATCGATCG diff --git a/pkg/obitools/obisuperkmer/obisuperkmer.go b/pkg/obitools/obisuperkmer/obisuperkmer.go new file mode 100644 index 0000000..c332564 --- /dev/null +++ b/pkg/obitools/obisuperkmer/obisuperkmer.go @@ -0,0 +1,10 @@ +// obisuperkmer function utility package. +// +// The obitools/obisuperkmer package contains every +// function specifically required by the obisuperkmer utility. +// +// The obisuperkmer command extracts super k-mers from DNA sequences. +// A super k-mer is a maximal subsequence where all consecutive k-mers +// share the same minimizer. This decomposition is useful for efficient +// k-mer indexing and analysis in bioinformatics applications. +package obisuperkmer diff --git a/pkg/obitools/obisuperkmer/options.go b/pkg/obitools/obisuperkmer/options.go new file mode 100644 index 0000000..2f25a8e --- /dev/null +++ b/pkg/obitools/obisuperkmer/options.go @@ -0,0 +1,69 @@ +package obisuperkmer + +import ( + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obitools/obiconvert" + "github.com/DavidGamba/go-getoptions" +) + +// Private variables for storing option values +var _KmerSize = 31 +var _MinimizerSize = 13 + +// SuperKmerOptionSet defines every option related to super k-mer extraction. +// +// The function adds to a CLI every option proposed to the user +// to tune the parameters of the super k-mer extraction algorithm. +// +// Parameters: +// - options: is a pointer to a getoptions.GetOpt instance normally +// produced by the obioptions.GenerateOptionParser function. +func SuperKmerOptionSet(options *getoptions.GetOpt) { + options.IntVar(&_KmerSize, "kmer-size", _KmerSize, + options.Alias("k"), + options.Description("Size of k-mers (must be between m+1 and 31).")) + + options.IntVar(&_MinimizerSize, "minimizer-size", _MinimizerSize, + options.Alias("m"), + options.Description("Size of minimizers (must be between 1 and k-1).")) +} + +// OptionSet adds to the basic option set every option declared for +// the obisuperkmer command. +// +// It takes a pointer to a GetOpt struct as its parameter and does not return anything. +func OptionSet(options *getoptions.GetOpt) { + obiconvert.OptionSet(false)(options) + SuperKmerOptionSet(options) +} + +// CLIKmerSize returns the k-mer size to use for super k-mer extraction. +// +// It does not take any parameters. +// It returns an integer representing the k-mer size. +func CLIKmerSize() int { + return _KmerSize +} + +// SetKmerSize sets the k-mer size for super k-mer extraction. +// +// Parameters: +// - k: the k-mer size (must be between m+1 and 31). +func SetKmerSize(k int) { + _KmerSize = k +} + +// CLIMinimizerSize returns the minimizer size to use for super k-mer extraction. +// +// It does not take any parameters. +// It returns an integer representing the minimizer size. +func CLIMinimizerSize() int { + return _MinimizerSize +} + +// SetMinimizerSize sets the minimizer size for super k-mer extraction. +// +// Parameters: +// - m: the minimizer size (must be between 1 and k-1). +func SetMinimizerSize(m int) { + _MinimizerSize = m +} diff --git a/pkg/obitools/obisuperkmer/superkmer.go b/pkg/obitools/obisuperkmer/superkmer.go new file mode 100644 index 0000000..d4bcf2e --- /dev/null +++ b/pkg/obitools/obisuperkmer/superkmer.go @@ -0,0 +1,59 @@ +package obisuperkmer + +import ( + log "github.com/sirupsen/logrus" + + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obidefault" + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiiter" + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obikmer" +) + +// CLIExtractSuperKmers extracts super k-mers from an iterator of BioSequences. +// +// This function takes an iterator of BioSequence objects, extracts super k-mers +// from each sequence using the k-mer and minimizer sizes specified by CLI options, +// and returns a new iterator yielding the extracted super k-mers as BioSequence objects. +// +// Each super k-mer is a maximal subsequence where all consecutive k-mers share +// the same minimizer. The resulting BioSequences contain metadata including: +// - minimizer_value: the canonical minimizer value +// - minimizer_seq: the DNA sequence of the minimizer +// - k: the k-mer size used +// - m: the minimizer size used +// - start: starting position in the original sequence +// - end: ending position in the original sequence +// - parent_id: ID of the parent sequence +// +// Parameters: +// - iterator: an iterator yielding BioSequence objects to process. +// +// Returns: +// - An iterator yielding BioSequence objects representing super k-mers. +func CLIExtractSuperKmers(iterator obiiter.IBioSequence) obiiter.IBioSequence { + // Get k-mer and minimizer sizes from CLI options + k := CLIKmerSize() + m := CLIMinimizerSize() + + // Validate parameters + if m < 1 || m >= k { + log.Fatalf("Invalid parameters: minimizer size (%d) must be between 1 and k-1 (%d)", m, k-1) + } + + if k < 2 || k > 31 { + log.Fatalf("Invalid k-mer size: %d (must be between 2 and 31)", k) + } + + log.Printf("Extracting super k-mers with k=%d, m=%d", k, m) + + // Create the worker for super k-mer extraction + worker := obikmer.SuperKmerWorker(k, m) + + // Apply the worker to the iterator with parallel processing + newIter := iterator.MakeIWorker( + worker, + false, // don't merge results + obidefault.ParallelWorkers(), + ) + + return newIter +} diff --git a/pkg/obitools/obisuperkmer/superkmer_test.go b/pkg/obitools/obisuperkmer/superkmer_test.go new file mode 100644 index 0000000..2d94019 --- /dev/null +++ b/pkg/obitools/obisuperkmer/superkmer_test.go @@ -0,0 +1,149 @@ +package obisuperkmer + +import ( + "testing" + + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiiter" + "git.metabarcoding.org/obitools/obitools4/obitools4/pkg/obiseq" +) + +func TestCLIExtractSuperKmers(t *testing.T) { + // Create a test sequence + testSeq := obiseq.NewBioSequence( + "test_seq", + []byte("ACGTACGTACGTACGTACGTACGTACGTACGT"), + "", + ) + + // Create a batch with the test sequence + batch := obiseq.NewBioSequenceBatch() + batch.Add(testSeq) + + // Create an iterator from the batch + iterator := obiiter.MakeBioSequenceBatchChannel(1) + go func() { + iterator.Push(batch) + iterator.Close() + }() + + // Set test parameters + SetKmerSize(15) + SetMinimizerSize(7) + + // Extract super k-mers + result := CLIExtractSuperKmers(iterator) + + // Count the number of super k-mers + count := 0 + for result.Next() { + batch := result.Get() + for _, sk := range batch.Slice() { + count++ + + // Verify that the super k-mer has the expected attributes + if !sk.HasAttribute("minimizer_value") { + t.Error("Super k-mer missing 'minimizer_value' attribute") + } + if !sk.HasAttribute("minimizer_seq") { + t.Error("Super k-mer missing 'minimizer_seq' attribute") + } + if !sk.HasAttribute("k") { + t.Error("Super k-mer missing 'k' attribute") + } + if !sk.HasAttribute("m") { + t.Error("Super k-mer missing 'm' attribute") + } + if !sk.HasAttribute("start") { + t.Error("Super k-mer missing 'start' attribute") + } + if !sk.HasAttribute("end") { + t.Error("Super k-mer missing 'end' attribute") + } + if !sk.HasAttribute("parent_id") { + t.Error("Super k-mer missing 'parent_id' attribute") + } + + // Verify attribute values + k, _ := sk.GetIntAttribute("k") + m, _ := sk.GetIntAttribute("m") + + if k != 15 { + t.Errorf("Expected k=15, got k=%d", k) + } + if m != 7 { + t.Errorf("Expected m=7, got m=%d", m) + } + + parentID, _ := sk.GetStringAttribute("parent_id") + if parentID != "test_seq" { + t.Errorf("Expected parent_id='test_seq', got '%s'", parentID) + } + } + } + + if count == 0 { + t.Error("No super k-mers were extracted") + } + + t.Logf("Extracted %d super k-mers from test sequence", count) +} + +func TestOptionGettersAndSetters(t *testing.T) { + // Test initial values + if CLIKmerSize() != 21 { + t.Errorf("Expected default k-mer size 21, got %d", CLIKmerSize()) + } + if CLIMinimizerSize() != 11 { + t.Errorf("Expected default minimizer size 11, got %d", CLIMinimizerSize()) + } + + // Test setters + SetKmerSize(25) + SetMinimizerSize(13) + + if CLIKmerSize() != 25 { + t.Errorf("SetKmerSize failed: expected 25, got %d", CLIKmerSize()) + } + if CLIMinimizerSize() != 13 { + t.Errorf("SetMinimizerSize failed: expected 13, got %d", CLIMinimizerSize()) + } + + // Reset to defaults + SetKmerSize(21) + SetMinimizerSize(11) +} + +func BenchmarkCLIExtractSuperKmers(b *testing.B) { + // Create a longer test sequence + longSeq := make([]byte, 1000) + bases := []byte{'A', 'C', 'G', 'T'} + for i := range longSeq { + longSeq[i] = bases[i%4] + } + + testSeq := obiseq.NewBioSequence("bench_seq", longSeq, "") + + // Set parameters + SetKmerSize(21) + SetMinimizerSize(11) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + batch := obiseq.NewBioSequenceBatch() + batch.Add(testSeq) + + iterator := obiiter.MakeBioSequenceBatchChannel(1) + go func() { + iterator.Push(batch) + iterator.Close() + }() + + result := CLIExtractSuperKmers(iterator) + + // Consume the iterator + for result.Next() { + result.Get() + } + } +}