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
76 changes: 76 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
name: Unit tests

on:
pull_request:
workflow_dispatch:

concurrency:
group: unit-tests-${{ github.ref }}
cancel-in-progress: true

jobs:
unit-tests:
strategy:
fail-fast: false
matrix:
include:
- label: ubuntu-x64
os: ubuntu-latest
arch: x64
make_cmd: make
- label: macos-arm64
os: macos-14
arch: arm64
make_cmd: gmake
runs-on: ${{ matrix.os }}
timeout-minutes: 45
name: ${{ matrix.label }}

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
submodules: recursive

- name: Set up Miniconda
uses: conda-incubator/setup-miniconda@v3
with:
activate-environment: test-env
auto-activate-base: false
python-version: "3.13"

- name: Install build dependencies (Ubuntu)
if: startsWith(matrix.os, 'ubuntu-')
shell: bash -el {0}
run: |
conda install -y -c conda-forge scons compilers boost

- name: Install build dependencies (macOS)
if: startsWith(matrix.os, 'macos-')
shell: bash -el {0}
run: |
brew install make
conda install -y -c conda-forge scons compilers boost

- name: Build libobjcryst
shell: bash -el {0}
run: |
sed -i.bak '/if gfb != afb:/,+1d' site_scons/libobjcrystbuildutils.py
PREFIX="$CONDA_PREFIX" python -m SCons -Q lib

- name: Locate built library
shell: bash -el {0}
run: |
libpath="$(find "$PWD/build" -type f \( -name 'libObjCryst.so' -o -name 'libObjCryst.dylib' \) | head -n 1)"
if [ -z "$libpath" ]; then
echo "No built libObjCryst library found under build/"
exit 1
fi
echo "LIBOBJCRYST_PATH=$libpath" >> "$GITHUB_ENV"
echo "Using LIBOBJCRYST_PATH=$libpath"

- name: Run standalone unit tests
shell: bash -el {0}
run: |
CONDA_PREFIX="$CONDA_PREFIX" ${{ matrix.make_cmd }} -C test LIBOBJCRYST="$LIBOBJCRYST_PATH"
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,7 @@ tags

# source distribution tarball
libobjcryst-*.tar.gz

# standalone unit-test artifacts
test/bin/
test/obj/
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Release notes

## Version 2026.2.0, - 2026-08-07

### Changed
- sync objcryst submodule to upstream `vincefn/objcryst` commit `4091cd9`, including:
- CIF parser robustness fix for truncated input
- fix for single-crystal simulation crashes
- refinement guards for missing data/phase and empty MonteCarloObj
- libobjcryst standalone workflow to build and run the upstream non-GUI unit-test suite

## Version 2026.1, - 2026-02-05

### Changed
Expand Down
2 changes: 1 addition & 1 deletion site_scons/fallback_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
Update FALLBACK_VERSION when tagging a new release.
'''

FALLBACK_VERSION = '2026.1.post0'
FALLBACK_VERSION = '2026.2.0.post0'
2 changes: 1 addition & 1 deletion src/objcryst
Submodule objcryst updated 61 files
+70 −0 .github/workflows/fox-build.yml
+86 −0 .github/workflows/non-gui-tests.yml
+34 −0 ChangeLog.txt
+1 −0 Fox/doc/source/index.rst
+34 −0 Fox/doc/source/testing.rst
+1 −1 Fox/gnu.mak
+10 −10 Fox/macosx.mak
+1 −3 Fox/src/foxgrid/FoxGridSlave.cpp
+1 −0 Fox/src/foxgrid/GridCommunication.cpp
+20 −12 ObjCryst/ObjCryst/CIF.cpp
+1 −1 ObjCryst/ObjCryst/Crystal.h
+27 −2 ObjCryst/ObjCryst/DiffractionDataSingleCrystal.cpp
+14 −0 ObjCryst/ObjCryst/IO.cpp
+30 −19 ObjCryst/ObjCryst/Indexing.cpp
+11 −3 ObjCryst/ObjCryst/Indexing.h
+104 −12 ObjCryst/ObjCryst/PowderPattern.cpp
+28 −1 ObjCryst/ObjCryst/PowderPattern.h
+6 −2 ObjCryst/ObjCryst/ReflectionProfile.cpp
+1 −1 ObjCryst/ObjCryst/ScatteringCorr.cpp
+11 −1 ObjCryst/ObjCryst/ScatteringData.cpp
+4 −0 ObjCryst/ObjCryst/ScatteringData.h
+2 −2 ObjCryst/ObjCryst/UnitCell.h
+24 −0 ObjCryst/doc/doc-main.h
+1 −1 ObjCryst/gnu.mak
+16 −14 ObjCryst/rules-gnu.mak
+1 −0 ObjCryst/wxCryst/wxDiffractionSingleCrystal.cpp
+20 −0 ObjCryst/wxCryst/wxGlobalOptimObj.cpp
+82 −18 ObjCryst/wxCryst/wxPowderPattern.cpp
+21 −0 test/.gitignore
+1 −0 test/Makefile
+74 −0 test/data/cif/PbSO4-COD-1528837.cif
+50 −0 test/data/ground_truth/powder_neutron_pv_gaussian_pbso4.txt
+81 −0 test/data/ground_truth/powder_neutron_pv_lorentzian_pbso4.txt
+10 −0 test/data/ground_truth/powder_xray_anisotropic_pbso4.txt
+53 −0 test/data/ground_truth/powder_xray_pv_gaussian_pbso4.txt
+81 −0 test/data/ground_truth/powder_xray_pv_lorentzian_pbso4.txt
+21 −0 test/data/ground_truth/singlecrystal_neutron_pbso4.txt
+21 −0 test/data/ground_truth/singlecrystal_xray_pbso4.txt
+44 −0 test/gnu.mak
+42 −0 test/integration/fox_nogui_cli.sh
+45 −0 test/integration/gnu.mak
+26 −0 test/integration/run_integration_tests.sh
+301 −0 test/integration/tutorial_cimetidine.cpp
+236 −0 test/integration/tutorial_pbso4.cpp
+37 −0 test/regression/fox_nogui_output_naming.sh
+12 −0 test/regression/gnu.mak
+72 −0 test/scripts/test_runner.sh
+123 −0 test/unit/api_cif.cpp
+124 −0 test/unit/api_crystal.cpp
+207 −0 test/unit/api_indexing.cpp
+101 −0 test/unit/api_molecule.cpp
+173 −0 test/unit/api_optimization.cpp
+374 −0 test/unit/api_powderpattern.cpp
+186 −0 test/unit/api_scattering.cpp
+144 −0 test/unit/api_spacegroup.cpp
+100 −0 test/unit/crystallography_workflow.cpp
+73 −0 test/unit/gnu.mak
+220 −0 test/unit/ground_truth.cpp
+79 −0 test/unit/run_unit_tests.sh
+279 −0 test/unit/test_common.h
+50 −0 test/unit/unit_cell_smoke.cpp
90 changes: 90 additions & 0 deletions test/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
ROOT_DIR := $(abspath $(CURDIR)/..)
OBJCRYST_TEST_DIR := $(ROOT_DIR)/src/objcryst/test
UNIT_SRC_DIR := $(OBJCRYST_TEST_DIR)/unit
BUILD ?= fast
ARCH ?= $(shell uname -m)
BUILD_DIR ?= $(ROOT_DIR)/build/$(BUILD)-$(ARCH)
LIBOBJCRYST ?= $(firstword $(wildcard $(BUILD_DIR)/src/libObjCryst*.so) \
$(wildcard $(BUILD_DIR)/src/libObjCryst*.dylib) \
$(wildcard $(BUILD_DIR)/src/libObjCryst*.dll) \
$(wildcard $(BUILD_DIR)/src/libObjCryst*.a) \
$(wildcard $(BUILD_DIR)/libObjCryst*.so) \
$(wildcard $(BUILD_DIR)/libObjCryst*.dylib) \
$(wildcard $(BUILD_DIR)/libObjCryst*.dll) \
$(wildcard $(BUILD_DIR)/libObjCryst*.a))
BIN_DIR := $(CURDIR)/bin/$(BUILD)-$(ARCH)
OBJ_DIR := $(CURDIR)/obj/$(BUILD)-$(ARCH)
RUNNER := $(CURDIR)/run_unit_tests.sh
TEST_RUNNER := $(CURDIR)/test_runner.sh

RM ?= rm -f
MKDIR_P ?= mkdir -p

CXX ?= c++
CXXFLAGS ?= -O2 -std=c++14
CPPFLAGS += -DREAL=double \
-I$(ROOT_DIR)/src \
-I$(ROOT_DIR)/src/objcryst \
-I$(ROOT_DIR)/src/objcryst/test/unit \
-I$(BUILD_DIR)/src \
-I$(BUILD_DIR)/src/objcryst \
-I$(BUILD_DIR)/src/version \
-I$(ROOT_DIR)/src/objcryst/ObjCryst \
-I$(ROOT_DIR)/src/objcryst/cctbx/include
LDFLAGS += -L$(abspath $(dir $(LIBOBJCRYST)))
LDLIBS += $(abspath $(LIBOBJCRYST))
ifeq ($(origin CONDA_PREFIX), undefined)
CONDA_PREFIX :=
endif

ifeq ($(shell uname -s),Darwin)
RPATH_FLAG = -Wl,-rpath,$(dir $(LIBOBJCRYST))
ifneq ($(strip $(CONDA_PREFIX)),)
RPATH_FLAG += -Wl,-rpath,$(CONDA_PREFIX)/lib
endif
else
RPATH_FLAG = -Wl,-rpath,$(dir $(LIBOBJCRYST))
endif

UNIT_TESTS := unit_cell_smoke crystallography_workflow \
api_spacegroup api_crystal api_molecule api_scattering \
api_powderpattern api_cif api_optimization api_indexing \
ground_truth

UNIT_BINS := $(addprefix $(BIN_DIR)/,$(UNIT_TESTS))
UNIT_OBJS := $(addprefix $(OBJ_DIR)/,$(addsuffix .o,$(UNIT_TESTS)))

.SECONDARY: $(UNIT_OBJS)

.PHONY: all build run clean tidy check-layout

all: build run

build: check-layout $(UNIT_BINS) $(RUNNER)

run: build $(RUNNER)
@cd $(UNIT_SRC_DIR) && TEST_TIMEOUT="$${FOX_TEST_TIMEOUT:-30s}" BUILD="$(BUILD)" "$(RUNNER)"

check-layout:
@test -d "$(UNIT_SRC_DIR)" || { echo "Missing upstream unit test sources at $(UNIT_SRC_DIR)"; exit 1; }
@test -f "$(TEST_RUNNER)" || { echo "Missing shared test runner at $(TEST_RUNNER)"; exit 1; }
@test -n "$(LIBOBJCRYST)" || { echo "No built libObjCryst found under $(BUILD_DIR). Build libobjcryst first."; exit 1; }
@test -f "$(ROOT_DIR)/src/objcryst/test/data/cif/PbSO4-COD-1528837.cif" || { echo "Missing required test data in src/objcryst/test/data"; exit 1; }

$(BIN_DIR) $(OBJ_DIR):
@$(MKDIR_P) "$@"

$(OBJ_DIR)/%.o: $(UNIT_SRC_DIR)/%.cpp | $(OBJ_DIR)
$(CXX) $(CPPFLAGS) $(CXXFLAGS) -c $< -o $@

$(BIN_DIR)/%: $(OBJ_DIR)/%.o | $(BIN_DIR)
$(CXX) $(CXXFLAGS) $(LDFLAGS) $(RPATH_FLAG) $< -o $@ $(LDLIBS)

$(RUNNER):
@chmod +x "$(RUNNER)"

tidy:
@$(RM) $(UNIT_OBJS)

clean: tidy
@$(RM) $(UNIT_BINS) "$(RUNNER)"
17 changes: 17 additions & 0 deletions test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
This directory provides an independent unit-test harness for running the
upstream ObjCryst non-GUI unit tests against the libobjcryst library built in
this repository.

Usage:

1. Build libobjcryst in-tree with SCons, for example:
`PREFIX=/path/to/env python -m SCons -Q lib`
2. Build and run the unit tests:
`CONDA_PREFIX=/path/to/env make -C test`

The makefile expects the library under `build/<build>-<arch>/src/` and uses
`build=fast` by default. Override `BUILD_DIR`, `BUILD`, `ARCH`, or `LIBOBJCRYST`
when using a different in-tree layout.

On macOS with a conda toolchain, pass `CONDA_PREFIX` so the test binaries add
the environment runtime library directory to their rpath.
72 changes: 72 additions & 0 deletions test/run_unit_tests.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env bash
set -eu

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
BIN_SUBDIR="${BUILD:-fast}-$(uname -m)"

# shellcheck source=/dev/null
. "$SCRIPT_DIR/test_runner.sh"

TEST_TIMEOUT="${FOX_TEST_TIMEOUT:-30s}"

run_test "unit::unit_cell_smoke" "$SCRIPT_DIR/bin/$BIN_SUBDIR/unit_cell_smoke"
run_test "unit::crystallography_workflow" "$SCRIPT_DIR/bin/$BIN_SUBDIR/crystallography_workflow"

run_test "unit::spacegroup" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_spacegroup" "spacegroup"
run_test "unit::spacegroup-alternate-settings" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_spacegroup" "spacegroup-alternate-settings"
run_test "unit::spacegroup-reflection-properties" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_spacegroup" "spacegroup-reflection-properties"
run_test "unit::spacegroup-symmetry-operations" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_spacegroup" "spacegroup-symmetry-operations"
run_test "unit::spacegroup-asymmetric-unit" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_spacegroup" "spacegroup-asymmetric-unit"

run_test "unit::scattering-power-atom" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_crystal" "scattering-power-atom"
run_test "unit::crystal-atom" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_crystal" "crystal-atom"
run_test "unit::crystal-scatterer-management" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_crystal" "crystal-scatterer-management"
run_test "unit::unitcell-geometry" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_crystal" "unitcell-geometry"

run_test "unit::molecule-atoms-bonds" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_molecule" "molecule-atoms-bonds"
run_test "unit::molecule-angles-dihedrals" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_molecule" "molecule-angles-dihedrals"
run_test "unit::molecule-formula-loglikelihood" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_molecule" "molecule-formula-loglikelihood"

run_test "unit::scatteringdata-singlecrystal" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_scattering" "scatteringdata-singlecrystal"
run_test "unit::scatteringdata-radiation-types" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_scattering" "scatteringdata-radiation-types"
run_test "unit::diffractiondata-observed" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_scattering" "diffractiondata-observed"
run_test "unit::singlecrystal-groundtruth-xray" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_scattering" "singlecrystal-groundtruth-xray"
run_test "unit::singlecrystal-groundtruth-neutron" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_scattering" "singlecrystal-groundtruth-neutron"
run_test "unit::singlecrystal-simulate-ungrouped" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_scattering" "singlecrystal-simulate-ungrouped"
run_test "unit::singlecrystal-simulate-grouped-equal" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_scattering" "singlecrystal-simulate-grouped-equal"
run_test "unit::singlecrystal-simulate-grouped-user" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_scattering" "singlecrystal-simulate-grouped-user"

run_test "unit::cif-import" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_cif" "cif-import"
run_test "unit::cif-data-fields" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_cif" "cif-data-fields"
run_test "unit::cif-coordinate-conversion" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_cif" "cif-coordinate-conversion"
run_test "unit::cif-truncated-values" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_cif" "cif-truncated-values"

run_test "unit::refinablepar" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_optimization" "refinablepar"
run_test "unit::refinableobj" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_optimization" "refinableobj"
run_test "unit::optimizationobj" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_optimization" "optimizationobj"
run_test "unit::optimizationobj-limits-options" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_optimization" "optimizationobj-limits-options"
run_test "unit::lsqnumobj" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_optimization" "lsqnumobj"
run_test "unit::lsqnumobj-residual-statistics" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_optimization" "lsqnumobj-residual-statistics"

run_test "unit::peaklist-simulate-volume" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_indexing" "peaklist-simulate-volume"
run_test "unit::peaklist-add-remove" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_indexing" "peaklist-add-remove"
run_test "unit::cellexplorer" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_indexing" "cellexplorer"
run_test "unit::cellexplorer-configuration" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_indexing" "cellexplorer-configuration"
run_test "unit::cellexplorer-dicvol-tetragonal" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_indexing" "cellexplorer-dicvol-tetragonal"
run_test "unit::cellexplorer-dicvol-monoclinic" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_indexing" "cellexplorer-dicvol-monoclinic"

run_test "unit::powderpattern-background" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_powderpattern" "powderpattern-background"
run_test "unit::powderpattern-diffraction" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_powderpattern" "powderpattern-diffraction"
run_test "unit::powderpattern-diffraction-mur" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_powderpattern" "powderpattern-diffraction-mur"
run_test "unit::powderpattern-diffraction-lebail-fhklobs" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_powderpattern" "powderpattern-diffraction-lebail-fhklobs"
run_test "unit::powderpattern-import" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_powderpattern" "powderpattern-import"
run_test "unit::scatteringcorr-subclasses" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_powderpattern" "scatteringcorr-subclasses"
run_test "unit::reflectionprofile-pseudo-voigt" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_powderpattern" "reflectionprofile-pseudo-voigt"
run_test "unit::reflectionprofile-double-exponential-pv" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_powderpattern" "reflectionprofile-double-exponential-pv"

run_test "unit::powder-groundtruth-xray-pv-gaussian" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_powderpattern" "powder-groundtruth-xray-pv-gaussian"
run_test "unit::powder-groundtruth-xray-pv-lorentzian" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_powderpattern" "powder-groundtruth-xray-pv-lorentzian"
run_test "unit::reflectionprofile-pv-anisotropic-direct" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_powderpattern" "reflectionprofile-pv-anisotropic-direct"
run_test "unit::powder-groundtruth-xray-anisotropic" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_powderpattern" "powder-groundtruth-xray-anisotropic"
run_test "unit::powder-groundtruth-neutron-pv-gaussian" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_powderpattern" "powder-groundtruth-neutron-pv-gaussian"
run_test "unit::powder-groundtruth-neutron-pv-lorentzian" "$SCRIPT_DIR/bin/$BIN_SUBDIR/api_powderpattern" "powder-groundtruth-neutron-pv-lorentzian"
8 changes: 8 additions & 0 deletions test/test_runner.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -eu

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"

# shellcheck source=/dev/null
. "$ROOT_DIR/src/objcryst/test/scripts/test_runner.sh"
Loading