diff --git a/quickTest/Makefile b/quickTest/Makefile index c1b6ecae..febd83e9 100644 --- a/quickTest/Makefile +++ b/quickTest/Makefile @@ -1,10 +1,10 @@ TEST_BASE = $(shell pwd) -.PHONY: FRC FVMModUnitTests FVMAdaptTest Containers Tools test default +.PHONY: FRC FVMModUnitTests FVMAdaptTest Containers Tools SystemTests test default -default: FVM FVMModUnitTests FVMAdaptTest Containers Tools - cat Containers/TestResults Tools/TestResults FVMModUnitTests/TestResults FVM/TestResults > TestResults - rm Containers/TestResults Tools/TestResults FVMModUnitTests/TestResults FVM/TestResults +default: FVM FVMModUnitTests FVMAdaptTest Containers Tools SystemTests + cat Containers/TestResults Tools/TestResults FVMModUnitTests/TestResults FVM/TestResults SystemTests/TestResults > TestResults + rm Containers/TestResults Tools/TestResults FVMModUnitTests/TestResults FVM/TestResults SystemTests/TestResults @grep PASS TestResults @grep FAIL TestResults || true @! grep -q FAIL TestResults @@ -26,6 +26,9 @@ Containers: FRC Tools: FRC $(MAKE) -C Tools LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) +SystemTests: FRC + $(MAKE) -C SystemTests LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) + clean: FRC rm -f TestResults $(MAKE) -C FVM LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean @@ -33,6 +36,7 @@ clean: FRC $(MAKE) -C Containers LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean $(MAKE) -C Tools LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean $(MAKE) -C FVMAdaptTest LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean + $(MAKE) -C SystemTests LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) clean distclean: rm -f TestResults @@ -41,3 +45,4 @@ distclean: $(MAKE) -C Containers LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean $(MAKE) -C Tools LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean $(MAKE) -C FVMAdaptTest LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean + $(MAKE) -C SystemTests LOCI_BASE=$(LOCI_BASE) TEST_BASE=$(TEST_BASE) distclean diff --git a/quickTest/SystemTests/Makefile b/quickTest/SystemTests/Makefile new file mode 100644 index 00000000..9ae62fd1 --- /dev/null +++ b/quickTest/SystemTests/Makefile @@ -0,0 +1,66 @@ +############################################################################### +# +# Copyright 2008-2025, Mississippi State University +# +# This file is part of the Loci Framework. +# +# The Loci Framework is free software: you can redistribute it and/or modify +# it under the terms of the Lesser GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# The Loci Framework is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# Lesser GNU General Public License for more details. +# +# You should have received a copy of the Lesser GNU General Public License +# along with the Loci Framework. If not, see +# +############################################################################### +# LOCI_BASE should be set before the Makefile +# Set TARGET to the name of your program +# Set FILES to list '.loci' files that will be compiled into your module, or + +########################################################################### +# No changes required below this line +########################################################################### + +include $(LOCI_BASE)/Loci.conf +include $(LOCI_BASE)/version.conf + +INCLUDES = -I../contrib/doctest -I$(LOCI_BASE)/include + +AUTOMATIC_FILES = $(call loci_compile_files,) +AUTOMATIC_OBJS = $(call loci_file2objs,$(AUTOMATIC_FILES)) +AUTOMATIC_TESTS= $(subst .o,.test, $(AUTOMATIC_OBJS)) +TESTS = $(AUTOMATIC_TESTS) + +TestResults: $(TESTS) + cat $(TESTS) > TestResults; rm $(TESTS) + +%.test: %.o + $(LD) -o $*.exe $^ $(LOCAL_LIBS) $(LIBS) $(LDFLAGS) + @(LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) DYLD_LIBRARY_PATH=$(DYLD_LIBRARY_PATH) ./$*.exe > $*.log 2>&1 || true) + tail -n 1 $*.log > $*.log1 + echo -n SystemTests/$*":" > $@ + @if grep -q "SUCCESS!" $*.log1 ; then echo " PASSED!"; else echo " FAILED!"; fi >>$@ + rm $*.log1 $*.exe + +clean: + rm -fr *.o *.exe *.d *.log *.log1 + +# Junk files that are created while editing and running cases +JUNK = $(wildcard *~) $(wildcard crash_dump.*) core debug output $(wildcard *.o) +# ".cc" files created from .loci files +LOCI_LPP_FILES = $(LOCI_FILES:.loci=.$(LPP_I_SUFFIX)) + +distclean: clean + rm -fr TestResults $(JUNK) $(LOCI_LPP_FILES) $(DEPEND_FILES) $(TESTS) + +DEPEND_FILES=$(subst .o,.d,$(OBJS)) + +#include automatically generated dependencies +ifeq ($(filter $(MAKECMDGOALS),clean distclean ),) +-include $(DEPEND_FILES) +endif diff --git a/quickTest/SystemTests/test_store.cc b/quickTest/SystemTests/test_store.cc new file mode 100644 index 00000000..d9f337bf --- /dev/null +++ b/quickTest/SystemTests/test_store.cc @@ -0,0 +1,283 @@ +#include + +#define DOCTEST_CONFIG_IMPLEMENT +#include + +#include +#include +#include + +using namespace Loci; + +namespace { + + entitySet set_of(std::initializer_list values) { + entitySet result; + for(std::initializer_list::const_iterator i = values.begin(); + i != values.end(); ++i) + result += *i; + return result; + } + + void fill(store &values, int offset) { + entitySet dom = values.domain(); + for(entitySet::const_iterator i = dom.begin(); i != dom.end(); ++i) + values[*i] = offset + *i; + } + + std::vector read_values(const store &values, + const entitySet &domain) { + std::vector result; + for(entitySet::const_iterator i = domain.begin(); i != domain.end(); ++i) + result.push_back(values[*i]); + return result; + } + +} // namespace + +TEST_CASE("store allocates sparse domains and exposes indexed values") { + entitySet dom = entitySet(interval(2, 4)) | interval(8, 8); + + store values; + values.allocate(dom); + fill(values, 100); + + CHECK(values.domain() == dom); + CHECK(values[2] == 102); + CHECK(values[4] == 104); + CHECK(values[8] == 108); +} + +TEST_CASE("store reallocation preserves values on the overlapping domain") { + store values; + values.allocate(interval(1, 4)); + fill(values, 100); + + values.allocate(interval(3, 6)); + + CHECK(values.domain() == entitySet(interval(3, 6))); + CHECK(values[3] == 103); + CHECK(values[4] == 104); + + values[5] = 205; + values[6] = 206; + values.allocate(interval(4, 5)); + + CHECK(values.domain() == entitySet(interval(4, 5))); + CHECK(values[4] == 104); + CHECK(values[5] == 205); + + values.allocate(EMPTY); + CHECK(values.domain() == EMPTY); + + values.allocate(interval(9, 9)); + values[9] = 909; + CHECK(values.domain() == entitySet(interval(9, 9))); + CHECK(values[9] == 909); +} + +TEST_CASE("store shift renumbers the domain without moving stored values") { + store values; + values.allocate(set_of({2, 4})); + values[2] = 20; + values[4] = 40; + + values.Rep()->shift(10); + + CHECK(values.domain() == set_of({12, 14})); + CHECK(values[12] == 20); + CHECK(values[14] == 40); +} + +TEST_CASE("store new_store creates an independent empty store of the same type") { + store source; + source.allocate(interval(1, 2)); + fill(source, 10); + + storeRepP fresh(source.Rep()->new_store(interval(7, 8))); + store created(fresh); + fill(created, 100); + + CHECK(isSTORE(fresh)); + CHECK(created.domain() == entitySet(interval(7, 8))); + CHECK(read_values(created, interval(7, 8)) == std::vector({107, 108})); + CHECK(read_values(source, interval(1, 2)) == std::vector({11, 12})); +} + +TEST_CASE("storeRep copy updates only the requested context") { + store source; + source.allocate(interval(0, 4)); + fill(source, 10); + + store target; + target.allocate(interval(0, 4)); + fill(target, -100); + + storeRepP source_rep = source.Rep(); + target.Rep()->copy(source_rep, set_of({1, 3})); + + CHECK(target[0] == -100); + CHECK(target[1] == 11); + CHECK(target[2] == -98); + CHECK(target[3] == 13); + CHECK(target[4] == -96); +} + +TEST_CASE("store pack_size reports the packable intersection") { + store source; + source.allocate(interval(2, 4)); + source[2] = 22; + source[3] = 33; + source[4] = 44; + + entitySet packed; + const entitySet requested = set_of({1, 2, 4, 9}); + int size = source.Rep()->pack_size(requested, packed); + + CHECK(packed == set_of({2, 4})); + CHECK(size == static_cast(2 * sizeof(int))); + + std::vector buffer(size); + int position = 0; + source.Rep()->pack(&buffer[0], position, size, packed); + CHECK(position == size); + + store target; + target.allocate(interval(20, 21)); + sequence unpack_order; + unpack_order += interval(20, 21); + + position = 0; + target.Rep()->unpack(&buffer[0], position, size, unpack_order); + CHECK(position == size); + + CHECK(read_values(target, interval(20, 21)) == std::vector({22, 44})); +} + +TEST_CASE("store one-argument pack_size ignores out-of-domain entities" * + doctest::may_fail()) { + store source; + source.allocate(interval(2, 4)); + + const entitySet requested = set_of({1, 2, 4, 9}); + + CHECK(source.Rep()->pack_size(requested) == + static_cast(2 * sizeof(int))); +} + +TEST_CASE("store gather and scatter move values through dMap images") { + store source; + source.allocate(interval(10, 12)); + source[10] = 1000; + source[11] = 1100; + source[12] = 1200; + + dMap gather_map; + gather_map.allocate(interval(0, 2)); + gather_map[0] = 12; + gather_map[1] = 10; + gather_map[2] = 11; + + store gathered; + gathered.allocate(interval(0, 2)); + storeRepP source_rep = source.Rep(); + gathered.Rep()->gather(gather_map, source_rep, interval(0, 2)); + + CHECK(gathered[0] == 1200); + CHECK(gathered[1] == 1000); + CHECK(gathered[2] == 1100); + + store scattered; + scattered.allocate(interval(20, 22)); + + dMap scatter_map; + scatter_map.allocate(interval(0, 2)); + scatter_map[0] = 21; + scatter_map[1] = 20; + scatter_map[2] = 22; + + storeRepP gathered_rep = gathered.Rep(); + scattered.Rep()->scatter(scatter_map, gathered_rep, interval(0, 2)); + + CHECK(scattered[20] == 1000); + CHECK(scattered[21] == 1200); + CHECK(scattered[22] == 1100); +} + +TEST_CASE("store remap creates a new store over the map image") { + store source; + source.allocate(interval(1, 3)); + source[1] = 7; + source[2] = 8; + source[3] = 9; + + dMap remap; + remap.allocate(interval(1, 3)); + remap[1] = 30; + remap[2] = 20; + remap[3] = 31; + + store result(source.Rep()->remap(remap)); + + CHECK(result.domain() == set_of({20, 30, 31})); + CHECK(result[20] == 8); + CHECK(result[30] == 7); + CHECK(result[31] == 9); +} + +TEST_CASE("store stream output can be read back into another store") { + store source; + source.allocate(set_of({3, 5, 6})); + source[3] = 13; + source[5] = 15; + source[6] = 16; + + std::ostringstream output; + source.Print(output); + + store parsed; + std::istringstream input(output.str()); + parsed.Input(input); + + CHECK(parsed.domain() == source.domain()); + CHECK(read_values(parsed, parsed.domain()) == std::vector({13, 15, 16})); +} + +TEST_CASE("store pack and unpack respects sequence ordering") { + store source; + source.allocate(interval(2, 4)); + source[2] = 22; + source[3] = 33; + source[4] = 44; + + int size = source.Rep()->pack_size(interval(2, 4)); + std::vector buffer(size); + int position = 0; + source.Rep()->pack(&buffer[0], position, size, interval(2, 4)); + CHECK(position == size); + + store target; + target.allocate(interval(10, 12)); + + sequence unpack_order; + unpack_order += interval(12, 10); + + position = 0; + target.Rep()->unpack(&buffer[0], position, size, unpack_order); + CHECK(position == size); + + CHECK(target[12] == 22); + CHECK(target[11] == 33); + CHECK(target[10] == 44); +} + +int main(int argc, char **argv) { + Loci::Init(&argc, &argv); + + doctest::Context context; + context.applyCommandLine(argc, argv); + const int result = context.run(); + + Loci::Finalize(); + return result; +}