Files
Jungfraujoch/tests/RugnuxInputKindTest.cpp
leonarski_f a78bfe951d rugnux: a model and a reference among the inputs, in any order, told by their contents
`rugnux <images> <model> <reflections>` in any order, without --model or -z. Each positional file
is classified by its contents (RugnuxInputKind.h, in JFJochRugnuxOutput - the library still reads
no files): an MTZ (magic) or an mmCIF with a _refln / _diffrn_refln loop is reference data (-z; the
input itself in --mode refine), a PDB file (a PDB record first, then ATOM/HETATM) or an mmCIF with
an _atom_site loop is a model (--model), anything else is images for the readers. CBF (###CBF or
_array_data) counts as images; a file whose first line is neither data_ nor a PDB record is decided
there, so image files are not read through. An mmCIF holding both atoms and reflections is refused
as ambiguous. At most one model and one reference, --model/--mr and -z included. Gzip read through.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
(cherry picked from commit 8f844b0bf86f338d20e8aed426a387722dd8d7cc)
2026-10-26 01:38:25 +01:00

80 lines
4.6 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#include <catch2/catch_all.hpp>
#include <filesystem>
#include <fstream>
#include <zlib.h>
#include "../rugnux/RugnuxInputKind.h"
namespace {
const std::string MODEL_CIF = "data_test\n#\n_cell.length_a 50.0\nloop_\n_atom_site.group_PDB\n_atom_site.id\n"
"ATOM 1\n";
const std::string SF_CIF = "data_r1abcsf\n_cell.length_a 50.0\nloop_\n_refln.index_h\n_refln.index_k\n"
"_refln.index_l\n_refln.intensity_meas\n1 0 0 10.0\n";
const std::string PDB = "HEADER TEST\nREMARK 1 SYNTHETIC\nCRYST1 50.000 50.000 50.000 90.00 90.00 90.00 P 1\n"
"ATOM 1 CA ALA A 1 1.000 2.000 3.000 1.00 10.00 C\nEND\n";
std::string Write(const std::string &name, const std::string &content) {
std::ofstream(name, std::ios::binary) << content;
return name;
}
std::string WriteGz(const std::string &name, const std::string &content) {
gzFile f = gzopen(name.c_str(), "wb");
gzwrite(f, content.data(), static_cast<unsigned>(content.size()));
gzclose(f);
return name;
}
}
TEST_CASE("RugnuxInputKind_Classify", "[rugnux_inputs]") {
using K = RugnuxInputKind;
CHECK(ClassifyRugnuxInput(Write("ik_model.cif", MODEL_CIF)) == K::Model);
CHECK(ClassifyRugnuxInput(Write("ik_sf.cif", SF_CIF)) == K::Reflections);
CHECK(ClassifyRugnuxInput(Write("ik_diffrn.cif", "data_x\nloop_\n_diffrn_refln.index_h\n1\n")) == K::Reflections);
CHECK(ClassifyRugnuxInput(Write("ik_model.pdb", PDB)) == K::Model);
CHECK(ClassifyRugnuxInput(WriteGz("ik_model.pdb.gz", PDB)) == K::Model);
CHECK(ClassifyRugnuxInput(WriteGz("ik_sf.cif.gz", SF_CIF)) == K::Reflections);
// the name says nothing: a model in a file called .mtz, reflections in one called .pdb
CHECK(ClassifyRugnuxInput(Write("ik_model_named.mtz", MODEL_CIF)) == K::Model);
CHECK(ClassifyRugnuxInput(Write("ik_mtz_named.pdb", std::string("MTZ \0\0\0\0", 8) + "binary")) == K::Reflections);
CHECK(ClassifyRugnuxInput(Write("ik_image.h5", std::string("\x89HDF\r\n\x1a\n\0\0\0\0", 12))) == K::Images);
CHECK(ClassifyRugnuxInput(Write("ik_image.cbf", "###CBF: VERSION 1.5\ndata_image\n_array_data.data\n")) == K::Images);
CHECK(ClassifyRugnuxInput(Write("ik_image2.cbf", "data_image\n_array_data.data\n_atom_site.x\n")) == K::Images);
CHECK(ClassifyRugnuxInput(Write("ik_image.img", "{\nHEADER_BYTES= 512;\nDIM=2;\n}")) == K::Images);
CHECK(ClassifyRugnuxInput(Write("ik_binary", std::string("\0\1\2\3\4\5\6\7\0\0\0\0\0\0\0\0x", 17))) == K::Images);
// A PDB file without an atom is not a model
CHECK(ClassifyRugnuxInput(Write("ik_noatom.pdb", "HEADER TEST\nEND\n")) == K::Images);
// Both coordinates and reflections: the user has to say
CHECK(ClassifyRugnuxInput(Write("ik_both.cif", MODEL_CIF + "loop_\n_refln.index_h\n1\n")) == K::Ambiguous);
for (const char *f: {"ik_model.cif", "ik_sf.cif", "ik_diffrn.cif", "ik_model.pdb", "ik_model.pdb.gz", "ik_sf.cif.gz",
"ik_model_named.mtz", "ik_mtz_named.pdb", "ik_image.h5", "ik_image.cbf", "ik_image2.cbf",
"ik_image.img", "ik_binary", "ik_noatom.pdb", "ik_both.cif"})
std::filesystem::remove(f);
}
TEST_CASE("RugnuxInputKind_SplitAnyOrder", "[rugnux_inputs]") {
const std::string img = Write("ik_s_image.h5", std::string("\x89HDF\r\n\x1a\n\0\0\0\0", 12));
const std::string model = Write("ik_s_model.pdb", PDB);
const std::string sf = Write("ik_s_sf.cif", SF_CIF);
for (const auto &order: std::vector<std::vector<std::string>>{{img, model, sf}, {model, sf, img}, {sf, img, model}}) {
const RugnuxPositionalInputs s = SplitRugnuxInputs(order);
CHECK(s.images == std::vector<std::string>{img});
CHECK(s.model == model);
CHECK(s.reflections == sf);
}
// image files keep their order; a path that is no file (yet) is left to the image readers
const RugnuxPositionalInputs s = SplitRugnuxInputs({"later_master.h5", model, img});
CHECK(s.images == std::vector<std::string>{"later_master.h5", img});
const std::string model2 = Write("ik_s_model2.cif", MODEL_CIF);
CHECK_THROWS(SplitRugnuxInputs({img, model, model2}));
CHECK_THROWS(SplitRugnuxInputs({sf, img, sf}));
const std::string both = Write("ik_s_both.cif", MODEL_CIF + "loop_\n_refln.index_h\n1\n");
CHECK_THROWS(SplitRugnuxInputs({img, both}));
for (const auto &f: {img, model, sf, model2, both})
std::filesystem::remove(f);
}