Compare commits

...

41 commits

Author SHA1 Message Date
bbdbc7eb12 WIP merge 2024-08-13 05:03:14 -04:00
1798225efb WIP, i1j1 2024-08-07 11:05:47 -04:00
c8f3e9c9d6 WIP 2024-08-06 07:58:32 -04:00
3fc9bcc24b WIP 2024-08-06 04:08:19 -04:00
e3dc5cb9d1 cleanup 2024-08-03 01:03:49 -04:00
061f566264 comment, test Ofast/s/z 2024-08-03 00:59:17 -04:00
b98111b7df ensure coalesce is always called 2024-08-03 00:44:22 -04:00
7b71e55f76 update sm_chunk_rank fn 2024-08-03 00:35:43 -04:00
9817b78013 remove sm_chunk_set with set/clr 2024-08-02 10:52:34 -04:00
ca107d889a coalesce_chunk() 2024-08-02 08:53:36 -04:00
4af769e4ef remove redundant code 2024-08-02 08:25:46 -04:00
8dcf0ab311 WIP new separate chunk fn 2024-08-02 07:59:56 -04:00
919f595d29 WIP cleanup, add populate_rle tests 2024-08-02 00:29:35 -04:00
eb6367f950 new sm_chunk_rank() 2024-08-01 06:16:48 -04:00
98c802a169 tests passing 2024-08-01 00:56:08 -04:00
3b1f4babd3 WIP fixed a few cases 2024-07-31 05:00:16 -04:00
76048df2f6 WIP 2024-07-30 21:28:48 -04:00
339c9b7351 fix /api/get_start_offset/roll 2024-07-29 08:25:37 -04:00
e5906249b4 use new set/unset/assign API 2024-07-29 05:31:55 -04:00
b032d61b63 WIP, fixed /qc 2024-07-29 04:50:48 -04:00
e4b21f3f0f WIP 2024-07-28 05:44:26 -04:00
d5c0f9abbc WIP 2024-07-28 04:54:29 -04:00
c19fecde2e WIP 2024-07-27 14:40:54 -04:00
40951e37e6 WIP 2024-07-27 13:16:37 -04:00
bf3840e183 WIP 2024-07-27 02:52:21 -04:00
abf73dd7b9 WIP 2024-07-26 10:49:09 -04:00
e36cf1c2b2 clr bit impl 2024-07-26 09:03:18 -04:00
1825f0d43f WIP 2024-07-26 07:30:59 -04:00
a0613a1ffb WIP 2024-07-26 06:42:48 -04:00
e85b39ad63 WIP clr, split 2024-07-25 08:28:38 -04:00
b78e706496 WIP clr RLE bit 2024-07-25 03:11:40 -04:00
30421d8bfe cleaner bit clr impl 2024-07-23 14:32:10 -04:00
020df515b6 cleaner bit set impl 2024-07-23 11:55:57 -04:00
90c70298d2 s/sizeof(__sm_idx_t)/SM_SIZEOF_OVERHEAD/g 2024-07-23 05:56:45 -04:00
b498607e84 genChunk 2024-07-23 05:49:01 -04:00
50b88a7e7c qc fixes 2024-07-22 09:33:44 -04:00
b0280271df PASS: /qc/__sm_chunk_get_position 2024-07-19 04:44:26 -04:00
dfda0017d5 Removing make for cmake 2024-07-16 06:20:45 -04:00
99035d8a0f WIP: moving things around 2024-07-16 06:20:28 -04:00
d99b1ac98d WIP 2024-07-15 10:37:16 -04:00
5ab1579123 WIP 2024-07-12 07:45:07 -04:00
31 changed files with 5053 additions and 2246 deletions

32
.editorconfig Normal file
View file

@ -0,0 +1,32 @@
# Editor configuration, see http://editorconfig.org
root = true
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
indent_style = space
indent_size = 2
# Ignore diffs/patches
[*.{diff,patch}]
end_of_line = unset
insert_final_newline = unset
trim_trailing_whitespace = unset
indent_size = unset
[{.*,secrets}/**]
end_of_line = unset
insert_final_newline = unset
trim_trailing_whitespace = unset
charset = unset
indent_style = unset
indent_size = unset
[*.py]
indent_size = 4
[*.md]
max_line_length = off
trim_trailing_whitespace = false

View file

@ -3,5 +3,8 @@
<clangFormatSettings>
<option name="ENABLED" value="true" />
</clangFormatSettings>
<editorconfig>
<option name="ENABLED" value="false" />
</editorconfig>
</code_scheme>
</component>

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CLionExternalBuildManager">
<target id="db0ccaeb-4851-470b-83d0-afa663f6ceb9" name="tests/soak" defaultType="MAKE">
<configuration id="98973a90-a9d0-431b-9071-9ce6960b0b01" name="tests/soak">
<build type="MAKE">
<make targetName="tests/soak" />
</build>
<clean type="MAKE">
<make targetName="clean" />
</clean>
</configuration>
</target>
</component>
</project>

View file

@ -1,25 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MakefileBuildTargetsManager">
<user-build-targets>
<build-target name="tests/soak">
<build-configurations>
<build-configuration>
<make-targets>
<make-target>tests/soak</make-target>
</make-targets>
</build-configuration>
</build-configurations>
</build-target>
<build-target name="clean">
<build-configurations>
<build-configuration>
<make-targets>
<make-target>clean</make-target>
</make-targets>
</build-configuration>
</build-configurations>
</build-target>
</user-build-targets>
</component>
</project>

View file

@ -4,28 +4,8 @@
<option name="executionMode" value="BINARY" />
<option name="pathToExecutable" value="$USER_HOME$/.nix-profile/bin/black" />
</component>
<component name="CidrRootsConfiguration">
<sourceRoots>
<file path="$PROJECT_DIR$/examples" />
<file path="$PROJECT_DIR$/include" />
<file path="$PROJECT_DIR$/lib" />
<file path="$PROJECT_DIR$/src" />
<file path="$PROJECT_DIR$/tests" />
</sourceRoots>
<component name="CMakePythonSetting">
<option name="pythonIntegrationState" value="YES" />
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="MakefileSettings">
<option name="linkedExternalProjectsSettings">
<MakefileProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
</set>
</option>
<option name="version" value="2" />
</MakefileProjectSettings>
</option>
</component>
<component name="MakefileWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
</project>

View file

@ -11,13 +11,12 @@ set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_C_OUTPUT_EXTENSION .o)
# Set source and header file locations
set(SOURCE_DIR src)
set(HEADER_DIR include)
set(SOURCE_DIR .)
set(HEADER_DIR . test)
set(COMMON_CMAKE_C_FLAGS "-Wall -Wextra -Wpedantic")
set(CMAKE_C_FLAGS_DEBUG "-DSPARSEMAP_DIAGNOSTIC -DDEBUG -g -O0")
set(CMAKE_C_FLAGS_PROFILE "-DSPARSEMAP_DIAGNOSTIC -DDEBUG -g -Og -fsanitize=address,leak,object-size,pointer-compare,pointer-subtract,null,return,bounds,pointer-overflow,undefined -fsanitize-address-use-after-scope")
set(COMMON_CMAKE_C_FLAGS "-std=c11 -Wall -Wextra -Wpedantic")
set(CMAKE_C_FLAGS_DEBUG "-DSPARSEMAP_DIAGNOSTIC -DSPARSEMAP_TESTING -DDEBUG -g -O0")
set(CMAKE_C_FLAGS_PROFILE "-DSPARSEMAP_DIAGNOSTIC -DSPARSEMAP_TESTING -DDEBUG -g -Og -fsanitize=address,leak,object-size,pointer-compare,pointer-subtract,null,return,bounds,pointer-overflow,undefined -fsanitize-address-use-after-scope")
set(CMAKE_C_FLAGS_RELEASE "-Ofast")
# Include all header files from the header directory
@ -50,44 +49,45 @@ set_target_properties(sparsemap PROPERTIES
target_include_directories(sparsemap PRIVATE ${HEADER_DIR})
# Add ex_1 program
add_executable(ex_1 examples/ex_1.c tests/munit.c lib/common.c)
add_executable(ex_1 test/ex_1.c test/munit.c test/qc.c test/common.c)
target_link_libraries(ex_1 PRIVATE sparsemap)
target_include_directories(ex_1 PRIVATE ${HEADER_DIR})
add_custom_target(run_ex_1 COMMAND ex_1 WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
# Add ex_2 program
add_executable(ex_2 examples/ex_2.c tests/munit.c lib/common.c)
add_executable(ex_2 test/ex_2.c test/munit.c test/qc.c test/common.c)
target_link_libraries(ex_2 PRIVATE sparsemap)
target_include_directories(ex_2 PRIVATE ${HEADER_DIR})
add_custom_target(run_ex_2 COMMAND ex_2 WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
# Add ex_3 program
add_executable(ex_3 examples/ex_3.c tests/munit.c lib/common.c)
add_executable(ex_3 test/ex_3.c test/munit.c test/qc.c test/common.c)
target_link_libraries(ex_3 PRIVATE sparsemap)
target_include_directories(ex_3 PRIVATE ${HEADER_DIR})
add_custom_target(run_ex_3 COMMAND ex_3 WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
# Add ex_4 program
add_executable(ex_4 examples/ex_4.c tests/munit.c lib/common.c)
add_executable(ex_4 test/ex_4.c test/munit.c test/qc.c test/common.c)
target_link_libraries(ex_4 PRIVATE sparsemap)
target_include_directories(ex_4 PRIVATE ${HEADER_DIR})
add_custom_target(run_ex_4 COMMAND ex_4 WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
# Add test program
add_executable(test tests/test.c tests/munit.c lib/common.c)
add_executable(test test/test.c test/munit.c test/qc.c test/common.c)
target_link_libraries(test PRIVATE sparsemap)
target_include_directories(test PRIVATE ${HEADER_DIR})
set_source_files_properties(test/test.c PROPERTIES COMPILE_FLAGS "-DDEBUG -DSPARSEMAP_DIAGNOSTIC -DSPARSEMAP_TESTING" )
add_custom_target(run_test COMMAND test WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
# Add soak program
add_executable(soak tests/soak.c lib/common.c lib/tdigest.c lib/roaring.c)
add_executable(soak test/soak.c test/common.c test/tdigest.c test/qc.c test/roaring.c)
target_link_libraries(soak PRIVATE sparsemap)
target_include_directories(soak PRIVATE ${HEADER_DIR} lib)
target_link_libraries(soak PUBLIC m)
add_custom_target(run_soak COMMAND soak WORKING_DIRECTORY ${CMAKE_BINARY_DIR})
# Add fuzzer program
# add_executable(fuzzer tests/fuzzer.c)
# add_executable(fuzzer EXCLUDE_FROM_ALL tests/fuzzer.c)
# target_link_libraries(fuzzer PRIVATE sparsemap)
# target_include_directories(fuzzer PRIVATE ${HEADER_DIR} lib)
# target_link_libraries(fuzzer PUBLIC m)

106
Makefile
View file

@ -1,106 +0,0 @@
OBJS = sparsemap.o
STATIC_LIB = libsparsemap.a
SHARED_LIB = libsparsemap.so
LIBS = -lm
#CFLAGS = -Wall -Wextra -Wpedantic -Of -std=c11 -Iinclude/ -fPIC
#CFLAGS = -Wall -Wextra -Wpedantic -Og -g -std=c11 -Iinclude/ -fPIC
#CFLAGS = -DSPARSEMAP_DIAGNOSTIC -DDEBUG -Wall -Wextra -Wpedantic -O0 -g -std=c11 -Iinclude/ -fPIC
CFLAGS = -DSPARSEMAP_DIAGNOSTIC -DDEBUG -Wall -Wextra -Ofast -g -std=c11 -Iinclude/ -fPIC
#CFLAGS = -Wall -Wextra -Wpedantic -Og -g -std=c11 -Iinclude/ -fPIC
#CFLAGS = -Wall -Wextra -Wpedantic -Ofast -g -std=c11 -Iinclude/ -fPIC
#CFLAGS = -DSPARSEMAP_DIAGNOSTIC -DDEBUG -Wall -Wextra -Wpedantic -Og -g -fsanitize=address,leak,object-size,pointer-compare,pointer-subtract,null,return,bounds,pointer-overflow,undefined -fsanitize-address-use-after-scope -std=c11 -Iinclude/ -fPIC
#CFLAGS = -Wall -Wextra -Wpedantic -Og -g -fsanitize=all -fhardened -std=c11 -Iinclude/ -fPIC
#TEST_FLAGS = -DDEBUG -Wall -Wextra -Wpedantic -O0 -g -std=c11 -Iinclude/ -Itests/ -fPIC
TEST_FLAGS = -Wall -Wextra -Wpedantic -Ofast -g -std=c11 -Iinclude/ -Itests/ -fPIC
#TEST_FLAGS = -Wall -Wextra -Wpedantic -Og -g -std=c11 -Iinclude/ -Itests/ -fPIC
#TEST_FLAGS = -DDEBUG -Wall -Wextra -Wpedantic -Og -g -fsanitize=address,leak,object-size,pointer-compare,pointer-subtract,null,return,bounds,pointer-overflow,undefined -fsanitize-address-use-after-scope -std=c11 -Iinclude/ -fPIC
TESTS = tests/test tests/soak
TEST_OBJS = tests/test.o lib/munit.o lib/tdigest.o lib/common.o
LIB_OBJS = lib/munit.o lib/tdigest.o lib/common.o lib/roaring.o
EXAMPLES = examples/ex_1 examples/ex_2 examples/ex_3 examples/ex_4
.PHONY: all shared static clean test examples mls
all: static shared
static: $(STATIC_LIB)
shared: $(SHARED_LIB)
$(STATIC_LIB): $(OBJS)
ar rcs $(STATIC_LIB) $?
$(SHARED_LIB): $(OBJS)
$(CC) $(CFLAGS) -o $@ $? -shared
examples: $(STATIC_LIB) $(EXAMPLES) $(TEST_OBJS)
mls: examples/mls
tests: $(TESTS)
test: tests
env ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./tests/test
soak: tests
env ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./tests/soak
fuzzer: tests
env ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./tests/fuzzer ./crash.case
tests/test: $(TEST_OBJS) $(LIB_OBJS) $(STATIC_LIB)
$(CC) $^ $(LIBS) -o $@ $(TEST_FLAGS)
clean:
rm -f $(OBJS)
rm -f examples/main.c
rm -f $(STATIC_LIB) $(SHARED_LIB)
rm -f $(TESTS) tests/*.o
rm -f $(EXAMPLES) examples/*.o
format:
clang-format -i src/sparsemap.c include/sparsemap.h examples/ex_*.c tests/soak.c tests/test.c tests/midl.c lib/common.c include/common.h
# clang-format -i include/*.h src/*.c tests/*.c tests/*.h examples/*.c
%.o: src/%.c
$(CC) $(CFLAGS) -c -o $@ $^
lib/%.o: tests/%.c
$(CC) $(CFLAGS) -c -o $@ $^
tests/%.o: tests/%.c
$(CC) $(CFLAGS) -c -o $@ $^
examples/%.o: examples/%.c
$(CC) $(CFLAGS) -c -o $@ $^
examples/ex_1: $(LIB_OBJS) examples/ex_1.o $(STATIC_LIB)
$(CC) $^ $(LIBS) -o $@ $(TEST_FLAGS)
examples/ex_2: $(LIB_OBJS) examples/ex_2.o $(STATIC_LIB)
$(CC) $^ $(LIBS) -o $@ $(TEST_FLAGS)
examples/ex_3: $(LIB_OBJS) examples/ex_3.o $(STATIC_LIB)
$(CC) $^ $(LIBS) -o $@ $(TEST_FLAGS)
examples/ex_4: $(LIB_OBJS) examples/ex_4.o $(STATIC_LIB)
$(CC) $^ $(LIBS) -o $@ $(TEST_FLAGS)
tests/soak: $(LIB_OBJS) tests/soak.o $(STATIC_LIB)
$(CC) $^ $(LIBS) -o $@ $(TEST_FLAGS)
tests/fuzzer: $(LIB_OBJS) tests/fuzzer.o $(STATIC_LIB)
$(CC) $^ $(LIBS) -o $@ $(TEST_FLAGS) -DFUZZ_DEBUG
todo:
rg -i 'todo|gsb|abort'
# cp src/sparsemap.c /tmp && clang-tidy src/sparsemap.c -fix -fix-errors -checks="readability-braces-around-statements" -- -DDEBUG -DSPARSEMAP_DIAGNOSTIC -DSPARSEMAP_ASSERT -Wall -Wextra -Wpedantic -Og -g -std=c11 -Iinclude/ -fPIC
# clear; make clean examples test && env ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./tests/test
# clear; make clean examples test && env ASAN_OPTIONS=detect_leaks=1 LSAN_OPTIONS=verbosity=1:log_threads=1 ./examples/soak

113
README.md
View file

@ -5,47 +5,106 @@ Unfortunately, they can use too much memory. To compensate, we often use
compressed bitmaps.
`sparsemap` is a sparse, compressed bitmap. In the best case, it can store 2048
bits in just 8 bytes. In the worst case, it stores the 2048 bits uncompressed and
requires an additional 8 bytes of overhead.
bits in just 8 bytes. In the worst case, it stores the 2048 bits uncompressed
and requires an additional 8 bytes of overhead.
The "best" case happens when large consecutive sequences of the bits are
either set ("1") or not set ("0"). If your numbers are consecutive 64bit
either set ("1") or not set ("0"). If your numbers are consecutive 64 bit
integers then sparsemap can compress up to 16kb in just 8 bytes.
## How does it work?
## How does it work? TODO... refine...
On the lowest level stores bits in sm_bitvec_t's (a uint32_t or uint64_t).
On the lowest level a bitmap contains a number of chunks. Each chunk has a
starting offset (`uint32_t`), a descriptor (the first `sm_bitvec_t`), and may
require a variable amount of additional space for encoding some bit patterns.
Each sm_bitvec_t has an additional descriptor (2 bits). A single word prepended
to each sm_bitvec_t describes its condition. The descriptor word and the
sm_bitvec_t's have the same size. The descriptor of a sm_bitvec_t
specifies whether the sm_bitvec_t consists only of set bits ("1"), unset
bits ("0") or has a mixed payload. In the first and second cases, the
sm_bitvec_t is not stored.
So, if the user sets bit 0 and bit 10000, and the chunk capacity is 2048,
the sparsemap creates two vectors; the first starts at offset 0, the second
starts at offset 8192. Offsets must align with the capacity of a vector.
An example shows a sequence of 4 x 16 bits (here, each sm_bitvec_t and the
Descriptor word has 16 bits):
Every 2 bit pair within the descriptor (the first vector size portion of the
chunk after the 4 bytes for the offset) indicates the encoded bit pattern at
that location's relative offset. This can be only set bits ("1"), only unset
bits ("0"), a mixed payload, or a run-length encoded extent of set bits
("1s"). A mixed vector consumes an additional `sm_bitvec_t`'s worth of space in
the buffer used to encode the bit pattern within that range.
Our examples below ignore the 4 byte overhead for the starting offset of these
chunks because they focus on the compressed encoding. Also, for brevity, we use
16 bit wide vectors (`sm_bitvec_t`), rather than 64 bits.
The first example, shows a sequence of 4 x 16 bits:
Descriptor:
00 00 00 00 11 00 11 10
^^ ^^ ^^ ^^-- sm_bitvec_t #0 - #3 are "0000000000000000"
^^-- sm_bitvec_t #4 is "1111111111111111"
^^-- sm_bitvec_t #5 is "0000000000000000"
^^-- sm_bitvec_t #6 is "1111111111111111"
^^-- sm_bitvec_t #7 is "0110010101111001"
^^ ^^ ^^ ^^-- sm_bitvec_t [0..3] are "0000000000000000"
^^-- sm_bitvec_t 4 is "1111111111111111"
^^-- sm_bitvec_t 5 is "0000000000000000"
^^-- sm_bitvec_t 6 is "1111111111111111"
^^-- sm_bitvec_t 7 is "0110010101111001"
Since the first 7 sm_bitvec_t's are either all "1" or "0" they are not stored.
The actual memory sequence looks like this:
Since the first 7 (0 through 6) `sm_bitvec_t`'s are either all "1" or "0" and
their encoding reqiures no additional storage in the buffer, so the actual
memory sequence for this chunk within the buffer looks like this:
0000000011001110 0110010101111001
0000000011001110 0110010101111001
Instead of storing 8 Words (16 bytes), we only store 2 Words (2 bytes): one
for the descriptor, and one for the last sm_bitvec_t #7.
Instead of storing 16 bytes, we only store 2 bytes: one for the descriptor, and
one for the last `sm_bitvec_t` #7.
The sparsemap stores a list of chunk maps, and for each chunk map, it stores the
absolute address (i.e. if the user sets bit 0 and bit 10000, and the chunk map
capacity is 2048, the sparsemap creates two chunk maps; the first starts at
offset 0, the second starts at offset 8192).
A 2nd example shows a chunk with reduced capacity.
Descriptor:
00 00 00 00 11 01 01 01
^^ ^^ ^^ ^^-- sm_bitvec_t [0..3] are "0000000000000000"
^^-- sm_bitvec_t 4 is "1111111111111111"
^^ ^^ ^^-- sm_bitvec_t [5..8] represent nothing
The memory sequence for this second, truncated chunk, looks like this:
0000000011010101
The bit pattern "01" can exist at the end of a chunk to indicate a reduced chunk
capacity. In this case the chunk's last 3 descriptors indicate that it can
encode up to 5 * 16 or 80 bit positions rather than the normal 128 (when using
16 bit wide vectors, `sm_bitvec_t`). When a chunk's capacity is entirely
truncated, it is empty and removed from the sparsemap entirely.
A 3rd example shows a single vector representing a long run of adjacent 1s
greater than the vector width (16 bits). Let's examine the representation:
Descriptor:
01 00 00 00 10 01 00 00
^^-- sm_bitvec_t #0 is '01' indicating a run-length encoding of 1s
^^ ^^ ^^ ^^ ^^ ^^ ^^-- the lenght of the run, 144
When (if, and only if) the first 2 bits of the descriptor are '01' they indicate
that this is an run-length encoded (RLE) vector. The number of 1s is the
remaining portion of the descriptor -- in this case 14 of the 16 bits -- encode
the run length. Simply mask the first two bits and interpret the remaining as an
`size_t`.
With that in mind, the memory sequence for this third example looks like this:
01 00 00 00 10 00 01 00
Which decodes to a run of 144 adjacent 1s:
1111 ... <then another 139 1s followed by the final> ... 1
The run must always be modulo the width of the descriptor (144 % 16 = 0). The
next chunk would encode any additional 1s adjacent to this set of 144 unless
there were 16 more, then this chunk would change to:
Descriptor:
01 00 00 00 10 10 00 00
^^-- sm_bitvec_t #0 is '01' meaning RLE a set of adjacent 1s
^^ ^^ ^^ ^^ ^^ ^^ ^^-- the new length of the run is 160
Using this method of RLE for adjacent 1s we can compress (again, in this case
where bitvec_t is 16 bits wide) 2^14 or 16348 adjacent 1s to the width of a
single descriptor, 2 bytes in this case, rather than the approximately 4096
bytes without RLE.
## Usage instructions

View file

@ -0,0 +1,65 @@
#!/usr/bin/env python
# Gererate a C function that contains a pre-calculated static table where each
# 8bit offset into that table encodes the required additional space for what is
# described.
# The 2 bit patters are:
# 00 -> 0 additional sm_bitvec_t (ZEROS)
# 11 -> 0 additional sm_bitvec_t (ONES)
# 10 -> 1 additional sm_bitvec_t (MIXED)
# 01 -> 0 additional sm_bitvec_t (NONE)
# The goal is to output this:
# /**
# * Calculates the number of sm_bitvec_ts required by a single byte with flags
# * (in m_data[0]).
# */
# static size_t
# __sm_chunk_calc_vector_size(uint8_t b)
# {
# // clang-format off
# static int lookup[] = {
# 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 2, 1, 0, 0, 1, 0,
# 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 2, 1, 0, 0, 1, 0,
# 1, 1, 2, 1, 1, 1, 2, 1, 2, 2, 3, 2, 1, 1, 2, 1,
# 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 2, 1, 0, 0, 1, 0,
# 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 2, 1, 0, 0, 1, 0,
# 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 2, 1, 0, 0, 1, 0,
# 1, 1, 2, 1, 1, 1, 2, 1, 2, 2, 3, 2, 1, 1, 2, 1,
# 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 2, 1, 0, 0, 1, 0,
# 1, 1, 2, 1, 1, 1, 2, 1, 2, 2, 3, 2, 1, 1, 2, 1,
# 1, 1, 2, 1, 1, 1, 2, 1, 2, 2, 3, 2, 1, 1, 2, 1,
# 2, 2, 3, 2, 2, 2, 3, 2, 3, 3, 4, 3, 2, 2, 3, 2,
# 1, 1, 2, 1, 1, 1, 2, 1, 2, 2, 3, 2, 1, 1, 2, 1,
# 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 2, 1, 0, 0, 1, 0,
# 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 2, 1, 0, 0, 1, 0,
# 1, 1, 2, 1, 1, 1, 2, 1, 2, 2, 3, 2, 1, 1, 2, 1,
# 0, 0, 1, 0, 0, 0, 1, 0, 1, 1, 2, 1, 0, 0, 1, 0
# };
# // clang-format on
# return (size_t)lookup[b];
# }
def create_lookup_table_c_format():
"""Creates a lookup table in C-style format."""
lookup_table = []
for byte in range(256):
count = 0
for i in range(3):
if (byte >> (i * 2)) & 3 == 2:
count += 1
lookup_table.append(count)
# Format the output as a C array
output = "static int lookup[] = {\n"
for i in range(0, 256, 16):
line = " " + ", ".join(str(x) for x in lookup_table[i:i+16]) + ",\n"
output += line
output += "};"
print(output)
if __name__ == "__main__":
create_lookup_table_c_format()

3389
sparsemap.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -29,31 +29,31 @@
*
* The implementation is separated into tiers.
*
* Tier 0 (lowest): bits are stored in a sm_bitvec_t (uint64_t).
* Tier 0 (lowest): bits are stored in a __sm_bitvec_t (uint64_t).
*
* Tier 1 (middle): multiple sm_bitvec_t are managed in a chunk map. The chunk
* map only stores those sm_bitvec_t that have a mixed payload of bits (i.e.
* some bits are 1, some are 0). As soon as ALL bits in a sm_bitvec_t are
* identical, this sm_bitvec_t is no longer stored, it is compressed.
* Tier 1 (middle): multiple __sm_bitvec_t are managed in a chunk map. The chunk
* map only stores those __sm_bitvec_t that have a mixed payload of bits (i.e.
* some bits are 1, some are 0). As soon as ALL bits in a __sm_bitvec_t are
* identical, this __sm_bitvec_t is no longer stored, it is compressed.
*
* The chunk maps store additional flags (2 bit) for each sm_bitvec_t in an
* additional word (same size as the sm_bitvec_t itself).
* The chunk maps store additional flags (2 bit) for each __sm_bitvec_t in an
* additional word (same size as the __sm_bitvec_t itself).
*
* 00 11 22 33
* ^-- descriptor for sm_bitvec_t 1
* ^-- descriptor for sm_bitvec_t 2
* ^-- descriptor for sm_bitvec_t 3
* ^-- descriptor for sm_bitvec_t 4
* ^-- descriptor for __sm_bitvec_t 1
* ^-- descriptor for __sm_bitvec_t 2
* ^-- descriptor for __sm_bitvec_t 3
* ^-- descriptor for __sm_bitvec_t 4
*
* Those flags (*) can have one of the following values:
*
* 00 The sm_bitvec_t is all zero -> sm_bitvec_t is not stored
* 11 The sm_bitvec_t is all one -> sm_bitvec_t is not stored
* 10 The sm_bitvec_t contains a bitmap -> sm_bitvec_t is stored
* 01 The sm_bitvec_t is not used (**)
* 00 The __sm_bitvec_t is all zero -> __sm_bitvec_t is not stored
* 11 The __sm_bitvec_t is all one -> __sm_bitvec_t is not stored
* 10 The __sm_bitvec_t contains a bitmap -> __sm_bitvec_t is stored
* 01 The __sm_bitvec_t is not used (**)
*
* The serialized size of a chunk map in memory therefore is at least
* one sm_bitvec_t for the flags, and (optionally) additional sm_bitvec_ts
* one __sm_bitvec_t for the flags, and (optionally) additional __sm_bitvec_ts
* if they are required.
*
* (*) The code comments often use the Erlang format for binary
@ -82,7 +82,7 @@ extern "C" {
* The public interface for a sparse bit-mapped index, a "sparse map".
*
* |sm_idx_t| is the user's numerical data type which is mapped to a single bit
* in the bitmap. Usually this is uint32_t or uint64_t. |sm_bitvec_t| is the
* in the bitmap. Usually this is uint32_t or uint64_t. |__sm_bitvec_t| is the
* storage type for a bit vector used by the __sm_chunk_t internal maps.
* Usually this is an uint64_t.
*/
@ -92,8 +92,6 @@ typedef size_t sparsemap_idx_t;
#define SPARSEMAP_IDX_MAX SIZE_MAX
#define SPARSEMAP_FOUND(x) ((x) != SPARSEMAP_IDX_MAX)
#define SPARSEMAP_NOT_FOUND(x) ((x) == SPARSEMAP_IDX_MAX)
typedef uint32_t sm_idx_t;
typedef uint64_t sm_bitvec_t;
/** @brief Allocate a new, empty sparsemap_t with a buffer of \b size on the
* heap to use for storage of bitmap data.
@ -229,7 +227,7 @@ size_t sparsemap_get_capacity(sparsemap_t *map);
*/
bool sparsemap_is_set(sparsemap_t *map, sparsemap_idx_t idx);
/** @brief Sets the bit at index \b idx to \b value.
/** @brief Assigns the bit at 0-based index \b idx to \b value.
*
* A sparsemap has a fixed size buffer with a capacity that can be exhausted by
* when calling this function. In such cases the return value is not equal to
@ -239,10 +237,41 @@ bool sparsemap_is_set(sparsemap_t *map, sparsemap_idx_t idx);
*
* @param[in] map The sparsemap reference.
* @param[in] idx The 0-based offset into the bitmap index to modify.
* @param[in] value When true idx set to 1, otherwise idx set to 0.
* @returns the \b idx supplied on success or SPARSEMAP_IDX_MAX on error
* with \b errno set to ENOSPC when the map is full.
*/
sparsemap_idx_t sparsemap_set(sparsemap_t *map, sparsemap_idx_t idx, bool value);
sparsemap_idx_t sparsemap_assign(sparsemap_t *map, sparsemap_idx_t idx, bool value);
/** @brief Sets the bit at 0-based index \b idx to 1.
*
* A sparsemap has a fixed size buffer with a capacity that can be exhausted by
* when calling this function. In such cases the return value is not equal to
* the provided \b idx and errno is set to ENOSPC. In such situations it is
* possible to grow the data size and retry the set() operation under certain
* circumstances (see #sparsemap() and #sparsemap_set_data_size()).
*
* @param[in] map The sparsemap reference.
* @param[in] idx The 0-based offset into the bitmap index to set to 1;
* @returns the \b idx supplied on success or SPARSEMAP_IDX_MAX on error
* with \b errno set to ENOSPC when the map is full.
*/
sparsemap_idx_t sparsemap_set(sparsemap_t *map, sparsemap_idx_t idx);
/** @brief Unsets the bit at 0-based index \b idx (sets it to 0).
*
* A sparsemap has a fixed size buffer with a capacity that can be exhausted by
* when calling this function. In such cases the return value is not equal to
* the provided \b idx and errno is set to ENOSPC. In such situations it is
* possible to grow the data size and retry the set() operation under certain
* circumstances (see #sparsemap() and #sparsemap_set_data_size()).
*
* @param[in] map The sparsemap reference.
* @param[in] idx The 0-based offset into the bitmap index to unset.
* @returns the \b idx supplied on success or SPARSEMAP_IDX_MAX on error
* with \b errno set to ENOSPC when the map is full.
*/
sparsemap_idx_t sparsemap_unset(sparsemap_t *map, sparsemap_idx_t idx);
/** @brief Returns the byte size of the data buffer that has been used thus far.
*
@ -265,9 +294,9 @@ void *sparsemap_get_data(sparsemap_t *map);
*/
size_t sparsemap_count(sparsemap_t *map);
/** @brief Returns the offset of the first bit set in the map.
/** @brief Returns the position of the first bit set in the map.
*
* This is the same as the value of the first set bit in the
* This is the same as the offset of the first set bit in the
* map.
*
* @param[in] map The sparsemap reference.
@ -302,7 +331,7 @@ double sparsemap_fill_factor(sparsemap_t *map);
* @param[in] skip Start the scan after \b skip position in the map.
* @param[in] aux Auxiliary information passed to the scanner.
*/
void sparsemap_scan(sparsemap_t *map, void (*scanner)(sm_idx_t vec[], size_t n, void *aux), size_t skip, void *aux);
void sparsemap_scan(sparsemap_t *map, void (*scanner)(uint32_t vec[], size_t n, void *aux), size_t skip, void *aux);
/** @brief Merges the values from \b source into \b destination, \b source is unchanged.
*
@ -312,21 +341,24 @@ void sparsemap_scan(sparsemap_t *map, void (*scanner)(sm_idx_t vec[], size_t n,
* @param[in] source The bitmap to merge into \b destination.
* @returns 0 on success, or sets errno to ENOSPC and returns the amount of
* additional space required to successfully merge the maps.
* @todo change return to SPARSEMAP_IDX_MAX on ENOSPC to match other functions
*/
int sparsemap_merge(sparsemap_t *destination, sparsemap_t *source);
/** @brief Splits the bitmap by assigning all bits starting at \b offset to the
/** @brief Splits the bitmap by assigning all bits starting at \b idx to the
* \b other bitmap while removing them from \b map.
*
* The \b other bitmap is expected to be empty.
* Splits into [start, idx), and [idx, end]. The \b other bitmap is
* expected to be empty.
*
* @param[in] map The sparsemap reference.
* @param[in] offset The 0-based offset into the bitmap at which to split, if
* @param[in] idx The 0-based idx into the bitmap at which to split, if
* set to SPARSEMAP_IDX_MAX then the bits will be evenly split.
* @param[in] other The bitmap into which we place the split.
* @returns the offset at which the map was split
* @returns the idx at which the map was split, or sets errno to ENOSPC and
* returns SPARSEMAP_IDX_MAX.
*/
sparsemap_idx_t sparsemap_split(sparsemap_t *map, sparsemap_idx_t offset, sparsemap_t *other);
sparsemap_idx_t sparsemap_split(sparsemap_t *map, sparsemap_idx_t idx, sparsemap_t *other);
/** @brief Finds the index of the \b n'th bit set to \b value.
*

File diff suppressed because it is too large Load diff

View file

@ -18,8 +18,8 @@
#endif
#endif
#include "../include/common.h"
#include "../include/sparsemap.h"
#include <common.h>
#include <sparsemap.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wvariadic-macros"
@ -51,6 +51,12 @@ tsc(void)
return 0;
}
// TODO remove me, this is only used for debugging.
#ifdef SPARSEMAP_TESTING
char *QCC_showSparsemap(void *value, int len);
char *QCC_showChunk(void *value, int len);
#endif
// get microsecond timestamp
uint64_t
msts()
@ -122,7 +128,7 @@ shuffle(int *array, size_t n)
}
}
int
static int
compare_ints(const void *a, const void *b)
{
return *(const int *)a - *(const int *)b;
@ -347,7 +353,7 @@ bitmap_from_uint32(sparsemap_t *map, uint32_t number)
{
for (int i = 0; i < 32; i++) {
bool bit = number & (1 << i);
sparsemap_set(map, i, bit);
sparsemap_assign(map, i, bit);
}
}
@ -398,7 +404,7 @@ sm_bitmap_from_uint64(sparsemap_t *map, int offset, uint64_t number)
{
for (int i = offset; i < 64; i++) {
bool bit = number & ((uint64_t)1 << i);
sparsemap_set(map, i, bit);
sparsemap_assign(map, i, bit);
}
}
@ -416,7 +422,7 @@ sm_add_span(sparsemap_t *map, int map_size, int span_length)
}
} while (attempts);
for (sparsemap_idx_t i = placed_at; i < placed_at + span_length; i++) {
if (sparsemap_set(map, i, true) != i) {
if (sparsemap_set(map, i) != i) {
return placed_at; // TODO error?
}
}

View file

@ -1,5 +1,5 @@
#include "../include/sparsemap.h"
#include <sparsemap.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wvariadic-macros"

View file

@ -4,7 +4,7 @@
#include <stdint.h>
#include <stdio.h>
#include "../include/sparsemap.h"
#include <sparsemap.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wvariadic-macros"
@ -35,16 +35,16 @@ main()
uint8_t buffer2[1024];
sparsemap_init(map, buffer, sizeof(buffer));
assert(sparsemap_get_size(map) == size);
sparsemap_set(map, 0, true);
sparsemap_set(map, 0);
assert(sparsemap_get_size(map) == size + 4 + 8 + 8);
assert(sparsemap_is_set(map, 0) == true);
assert(sparsemap_get_size(map) == size + 4 + 8 + 8);
assert(sparsemap_is_set(map, 1) == false);
sparsemap_set(map, 0, false);
sparsemap_unset(map, 0);
assert(sparsemap_get_size(map) == size);
sparsemap_clear(map);
sparsemap_set(map, 64, true);
sparsemap_set(map, 64);
assert(sparsemap_is_set(map, 64) == true);
assert(sparsemap_get_size(map) == size + 4 + 8 + 8);
@ -54,7 +54,7 @@ main()
// set [0..100000]
for (int i = 0; i < 100000; i++) {
assert(sparsemap_is_set(map, i) == false);
sparsemap_set(map, i, true);
sparsemap_set(map, i);
if (i > 5) {
for (int j = i - 5; j <= i; j++) {
assert(sparsemap_is_set(map, j) == true);
@ -73,7 +73,7 @@ main()
// unset [0..10000]
for (int i = 0; i < 10000; i++) {
assert(sparsemap_is_set(map, i) == true);
sparsemap_set(map, i, false);
sparsemap_unset(map, i);
assert(sparsemap_is_set(map, i) == false);
}
@ -87,7 +87,7 @@ main()
// set [10000..0]
for (int i = 10000; i >= 0; i--) {
assert(sparsemap_is_set(map, i) == false);
sparsemap_set(map, i, true);
sparsemap_set(map, i);
assert(sparsemap_is_set(map, i) == true);
}
@ -106,7 +106,7 @@ main()
// unset [10000..0]
for (int i = 10000; i >= 0; i--) {
assert(sparsemap_is_set(map, i) == true);
sparsemap_set(map, i, false);
sparsemap_unset(map, i);
assert(sparsemap_is_set(map, i) == false);
}
@ -117,13 +117,13 @@ main()
fprintf(stderr, ".");
sparsemap_clear(map);
sparsemap_set(map, 0, true);
sparsemap_set(map, 2048 * 2 + 1, true);
sparsemap_set(map, 0);
sparsemap_set(map, 2048 * 2 + 1);
assert(sparsemap_is_set(map, 0) == true);
assert(sparsemap_is_set(map, 2048 * 2 + 0) == false);
assert(sparsemap_is_set(map, 2048 * 2 + 1) == true);
assert(sparsemap_is_set(map, 2048 * 2 + 2) == false);
sparsemap_set(map, 2048, true);
sparsemap_set(map, 2048);
assert(sparsemap_is_set(map, 0) == true);
assert(sparsemap_is_set(map, 2047) == false);
assert(sparsemap_is_set(map, 2048) == true);
@ -137,7 +137,7 @@ main()
fprintf(stderr, ".");
for (int i = 0; i < 100000; i++) {
sparsemap_set(map, i, true);
sparsemap_set(map, i);
}
for (int i = 0; i < 100000; i++) {
assert(sparsemap_select(map, i, true) == (unsigned)i);
@ -147,7 +147,7 @@ main()
fprintf(stderr, ".");
for (int i = 1; i < 513; i++) {
sparsemap_set(map, i, true);
sparsemap_set(map, i);
}
for (int i = 1; i < 513; i++) {
assert(sparsemap_select(map, i - 1, true) == (unsigned)i);
@ -157,7 +157,7 @@ main()
fprintf(stderr, ".");
for (size_t i = 0; i < 8; i++) {
sparsemap_set(map, i * 10, true);
sparsemap_set(map, i * 10);
}
for (size_t i = 0; i < 8; i++) {
assert(sparsemap_select(map, i, true) == (sparsemap_idx_t)i * 10);
@ -168,7 +168,7 @@ main()
sparsemap_init(sm2, buffer2, sizeof(buffer2));
sparsemap_clear(sm2);
for (int i = 0; i < 2048 * 2; i++) {
sparsemap_set(map, i, true);
sparsemap_set(map, i);
}
sparsemap_split(map, 2048, sm2);
for (int i = 0; i < 2048; i++) {
@ -185,7 +185,7 @@ main()
sparsemap_init(sm2, buffer2, sizeof(buffer2));
sparsemap_clear(map);
for (int i = 0; i < 2048 * 3; i++) {
sparsemap_set(map, i, true);
sparsemap_set(map, i);
assert(sparsemap_is_set(map, i) == true);
}
sparsemap_split(map, 64, sm2);

View file

@ -3,7 +3,7 @@
#include <stdio.h>
#include <stdlib.h>
#include "../include/sparsemap.h"
#include <sparsemap.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wvariadic-macros"
@ -33,13 +33,13 @@ main(void)
// when the map is full.
for (i = 0; i < 7744; i++) {
if (!i % 2) {
sparsemap_set(map, i, true);
sparsemap_set(map, i);
assert(sparsemap_is_set(map, i) == true);
}
}
// On 1024 KiB of buffer with every other bit set the map holds 7744 bits
// and then runs out of space. This next _set() call will fail.
sparsemap_set(map, ++i, true);
sparsemap_set(map, ++i);
assert(sparsemap_is_set(map, i) == true);
return 0;
}

View file

@ -62,11 +62,11 @@ main(void)
// set all the bits on in a random order
for (i = 0; i < 1024; i++) {
__diag("set %d\n", array[i]);
sparsemap_set(map, array[i], true);
sparsemap_set(map, array[i]);
assert(sparsemap_is_set(map, array[i]) == true);
}
sparsemap_set(map, 1025, true);
sparsemap_set(map, 1025);
assert(sparsemap_is_set(map, 1025) == true);
return 0;

View file

@ -35,7 +35,7 @@ main(void)
// set all the bits on in a random order
for (i = 0; i < TEST_ARRAY_SIZE; i++) {
sparsemap_set(map, array[i], true);
sparsemap_set(map, array[i]);
assert(sparsemap_is_set(map, array[i]) == true);
}
@ -55,7 +55,7 @@ main(void)
shuffle(array, TEST_ARRAY_SIZE);
print_spans(array, TEST_ARRAY_SIZE);
for (i = 0; i < TEST_ARRAY_SIZE; i++) {
sparsemap_set(map, array[i], true);
sparsemap_set(map, array[i]);
assert(sparsemap_is_set(map, array[i]) == true);
}
has_span(map, array, TEST_ARRAY_SIZE, (int)len);

831
test/qc.c Normal file
View file

@ -0,0 +1,831 @@
/********************************************************************
* Copyright (c) 2014, Andrea Zito
* All rights reserved.
*
* License: BSD3
********************************************************************/
#include <stddef.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include "qc.h"
// TODO: fix these...
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wimplicit-function-declaration"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wint-conversion"
#pragma GCC diagnostic ignored "-Wpedantic"
typedef void (*QCC_genRaw)(void *ptr);
typedef void (*QCC_genRawR)(void *ptr, void *from, void *to);
struct QCC_Stamp {
char *label;
int n;
struct QCC_Stamp *next;
};
typedef struct QCC_Result {
QCC_TestStatus status;
QCC_Stamp *stamps;
QCC_GenValue **arguments;
int argumentsN;
} QCC_Result;
enum QCC_deref_type { NONE, LONG, INT, FLOAT, DOUBLE, BYTE, CHAR };
void
QCC_init(int seed)
{
if (seed) {
srandom(seed);
} else {
srandom(time(NULL));
}
}
/***********************************************************************
* Generators helper functions
***********************************************************************/
static char *
QCC_showSimpleValue(void *value, enum QCC_deref_type dt, int maxsize, const char *format)
{
char *vc = malloc(sizeof(char) * (maxsize + 1));
switch (dt) {
case LONG:
snprintf(vc, maxsize + 1, format, *(long *)value);
break;
case INT:
snprintf(vc, maxsize + 1, format, *(int *)value);
break;
case FLOAT:
snprintf(vc, maxsize + 1, format, *(float *)value);
break;
case DOUBLE:
snprintf(vc, maxsize + 1, format, *(double *)value);
break;
case CHAR:
snprintf(vc, maxsize + 1, format, *(char *)value);
break;
case BYTE:
snprintf(vc, maxsize + 1, format, *(uint8_t *)value);
break;
default:
snprintf(vc, maxsize + 1, format, *(int *)value);
break;
}
vc[maxsize] = 0;
return vc;
}
static void
QCC_freeSimpleValue(void *value)
{
free(value);
}
QCC_GenValue *
QCC_initGenValue(void *value, int n, QCC_showValue show, QCC_freeValue free)
{
QCC_GenValue *gv = malloc(sizeof(QCC_GenValue));
*gv = (QCC_GenValue) { .value = value, .n = n, .show = show, .free = free };
return gv;
}
/***********************************************************************
* Generators implementations
***********************************************************************/
static char *
QCC_showLong(void *value, int len)
{
return QCC_showSimpleValue(value, LONG, 21, "%ld");
}
void
QCC_genLongAtR(long *l, long *from, long *to)
{
long _from = *from;
long _to = *to;
unsigned long n = _to - _from;
if (n > RAND_MAX) {
_from = _from + n / 2 - RAND_MAX / 2;
_to = _from + n / 2 + RAND_MAX / 2;
n = _to - _from;
}
*l = (random() % n) + _from;
}
void
QCC_genLongAt(long *l)
{
long from = QCC_LONG_FROM;
long to = QCC_LONG_TO;
QCC_genLongAtR(l, &from, &to);
}
QCC_GenValue *
QCC_genLongR(long from, long to)
{
long *v = malloc(sizeof(long));
QCC_genLongAtR(v, &from, &to);
return QCC_initGenValue(v, 1, QCC_showLong, QCC_freeSimpleValue);
}
QCC_GenValue *
QCC_genLong()
{
return QCC_genLongR(QCC_LONG_FROM, QCC_LONG_TO);
}
static char *
QCC_showInt(void *value, int len)
{
return QCC_showSimpleValue(value, INT, 11, "%d");
}
void
QCC_genIntAtR(int *i, int *from, int *to)
{
int n = *to - *from;
*i = (random() % n) + *from;
}
void
QCC_genIntAt(int *i)
{
int from = QCC_INT_FROM;
int to = QCC_INT_TO;
QCC_genIntAtR(i, &from, &to);
}
QCC_GenValue *
QCC_genIntR(int from, int to)
{
int *v = malloc(sizeof(int));
QCC_genIntAtR(v, &from, &to);
return QCC_initGenValue(v, 1, QCC_showInt, QCC_freeSimpleValue);
}
QCC_GenValue *
QCC_genInt()
{
return QCC_genIntR(QCC_INT_FROM, QCC_INT_TO);
}
static char *
QCC_showDouble(void *value, int len)
{
return QCC_showSimpleValue(value, DOUBLE, 50, "%.12e");
}
void
QCC_genDoubleAtR(double *d, double *from, double *to)
{
double r = (double)random() / (double)RAND_MAX;
*d = *from + (*to - *from) * r;
}
void
QCC_genDoubleAt(double *d)
{
double from = QCC_DOUBLE_FROM;
double to = QCC_DOUBLE_TO;
QCC_genDoubleAtR(d, &from, &to);
}
QCC_GenValue *
QCC_genDoubleR(double from, double to)
{
double *v = malloc(sizeof(double));
QCC_genDoubleAtR(v, &from, &to);
return QCC_initGenValue(v, 1, QCC_showDouble, QCC_freeSimpleValue);
}
QCC_GenValue *
QCC_genDouble()
{
return QCC_genDoubleR(QCC_DOUBLE_FROM, QCC_DOUBLE_TO);
}
static char *
QCC_showFloat(void *value, int len)
{
return QCC_showSimpleValue(value, FLOAT, 50, "%.12e");
}
void
QCC_genFloatAtR(float *f, float *from, float *to)
{
float r = (float)random() / (float)RAND_MAX;
*f = *from + (*to - *from) * r;
}
void
QCC_genFloatAt(float *f)
{
float from = QCC_FLOAT_FROM;
float to = QCC_FLOAT_TO;
QCC_genFloatAtR(f, &from, &to);
}
QCC_GenValue *
QCC_genFloatR(float from, float to)
{
float *v = malloc(sizeof(float));
QCC_genFloatAtR(v, &from, &to);
return QCC_initGenValue(v, 1, QCC_showFloat, QCC_freeSimpleValue);
}
QCC_GenValue *
QCC_genFloat()
{
return QCC_genFloatR(QCC_FLOAT_FROM, QCC_FLOAT_TO);
}
static char *
QCC_showBoolean(void *value, int len)
{
QCC_Boolean *b = (QCC_Boolean *)value;
if (*b) {
return strdup("TRUE");
} else {
return strdup("FALSE");
}
}
void
QCC_genBooleanAt(QCC_Boolean *b)
{
double r = (double)random() / (double)RAND_MAX;
*b = r > 0.5 ? QCC_TRUE : QCC_FALSE;
}
QCC_GenValue *
QCC_genBoolean()
{
QCC_Boolean *v = malloc(sizeof(QCC_Boolean));
QCC_genBooleanAt(v);
return QCC_initGenValue(v, 1, QCC_showBoolean, QCC_freeSimpleValue);
}
static char *
QCC_showChar(void *value, int len)
{
return QCC_showSimpleValue(value, CHAR, 3, "'%c'");
}
void
QCC_genCharAt(char *c)
{
*c = (char)(random() % 93) + 33;
}
QCC_GenValue *
QCC_genChar()
{
char *v = malloc(sizeof(char));
QCC_genCharAt(v);
return QCC_initGenValue(v, 1, QCC_showChar, QCC_freeSimpleValue);
}
/***********************************************************************
* Array generators implementations
***********************************************************************/
QCC_GenValue *
QCC_genArrayOf(int len, QCC_genRaw elemGen, size_t elemSize, QCC_showValue show, QCC_freeValue free)
{
int n = random() % len;
uint8_t *arr = malloc(n * elemSize);
int p, i;
for (i = 0, p = 0; i < n; i++, p += elemSize)
elemGen(arr + p);
return QCC_initGenValue(arr, n, show, free);
}
QCC_GenValue *
QCC_genArrayOfR(int len, QCC_genRawR elemGen, void *from, void *to, size_t elemSize, QCC_showValue show, QCC_freeValue free)
{
int n = random() % len;
uint8_t *arr = malloc(n * elemSize);
int p, i;
for (i = 0, p = 0; i < n; i++, p += elemSize)
elemGen(arr + p, from, to);
return QCC_initGenValue(arr, n, show, free);
}
static char *
QCC_showString(void *value, int len)
{
return QCC_showSimpleValue(value, NONE, len, "%s");
}
QCC_GenValue *
QCC_genStringL(int len)
{
QCC_GenValue *s = QCC_genArrayOf(len, (QCC_genRaw)QCC_genCharAt, sizeof(char), QCC_showString, QCC_freeSimpleValue);
((char *)s->value)[s->n - 1] = '\0';
return s;
}
QCC_GenValue *
QCC_genString()
{
return QCC_genStringL(50);
}
static char *
QCC_showSimpleArray(void *value, size_t elemSize, QCC_showValue showValue, int len)
{
char **valStr = malloc(sizeof(char *) * len);
int valStrLen = 0;
int i;
for (i = 0; i < len; i++) {
valStr[i] = showValue(((uint8_t *)value) + (i * elemSize), 1);
valStrLen += strlen(valStr[i]);
}
int totStrLen = valStrLen + 2 + 2 * len + 1;
char *str = malloc(sizeof(char) * totStrLen);
sprintf(str, "[");
int currLen = 1;
for (i = 0; i < len; i++) {
if (i == 0) {
sprintf(str + currLen, "%s", valStr[i]);
} else {
sprintf(str + currLen, ", %s", valStr[i]);
currLen += 2;
}
currLen += strlen(valStr[i]);
free(valStr[i]);
}
sprintf(str + currLen, "]");
free(valStr);
return str;
}
static char *
QCC_showByte(void *value, int len)
{
return QCC_showSimpleValue(value, BYTE, 3, "'%x'");
}
static char *
QCC_showArrayByte(void *value, int n)
{
return QCC_showSimpleArray(value, sizeof(long), QCC_showByte, n);
}
void
QCC_genByteAtR(uint8_t *b, uint8_t *from, uint8_t *to)
{
uint8_t r = (uint8_t)random() / (uint8_t)RAND_MAX;
*b = *from + (*to - *from) * r;
}
void
QCC_genByteAt(uint8_t *b)
{
*b = ((uint8_t)random() / (uint8_t)RAND_MAX) % UINT8_MAX;
}
QCC_GenValue *
QCC_genArrayByteLR(int len, long from, long to)
{
return QCC_genArrayOfR(len, (QCC_genRawR)QCC_genByteAtR, &from, &to, sizeof(long), QCC_showArrayByte, QCC_freeSimpleValue);
}
QCC_GenValue *
QCC_genArrayByteL(int len)
{
return QCC_genArrayOf(len, (QCC_genRaw)QCC_genByteAt, sizeof(long), QCC_showArrayByte, QCC_freeSimpleValue);
}
QCC_GenValue *
QCC_genArrayByte()
{
return QCC_genArrayOf(50, (QCC_genRaw)QCC_genByteAt, sizeof(long), QCC_showArrayByte, QCC_freeSimpleValue);
}
static char *
QCC_showArrayLong(void *value, int n)
{
return QCC_showSimpleArray(value, sizeof(long), QCC_showLong, n);
}
QCC_GenValue *
QCC_genArrayLongLR(int len, long from, long to)
{
return QCC_genArrayOfR(len, (QCC_genRawR)QCC_genLongAtR, &from, &to, sizeof(long), QCC_showArrayLong, QCC_freeSimpleValue);
}
QCC_GenValue *
QCC_genArrayLongL(int len)
{
return QCC_genArrayOf(len, (QCC_genRaw)QCC_genLongAt, sizeof(long), QCC_showArrayLong, QCC_freeSimpleValue);
}
QCC_GenValue *
QCC_genArrayLong()
{
return QCC_genArrayOf(50, (QCC_genRaw)QCC_genLongAt, sizeof(long), QCC_showArrayLong, QCC_freeSimpleValue);
}
static char *
QCC_showArrayInt(void *value, int n)
{
return QCC_showSimpleArray(value, sizeof(int), QCC_showInt, n);
}
QCC_GenValue *
QCC_genArrayIntLR(int len, int from, int to)
{
return QCC_genArrayOfR(len, (QCC_genRawR)QCC_genIntAtR, &from, &to, sizeof(int), QCC_showArrayInt, QCC_freeSimpleValue);
}
QCC_GenValue *
QCC_genArrayIntL(int len)
{
return QCC_genArrayOf(len, (QCC_genRaw)QCC_genIntAt, sizeof(int), QCC_showArrayInt, QCC_freeSimpleValue);
}
QCC_GenValue *
QCC_genArrayInt()
{
return QCC_genArrayOf(50, (QCC_genRaw)QCC_genIntAt, sizeof(int), QCC_showArrayInt, QCC_freeSimpleValue);
}
static char *
QCC_showArrayDouble(void *value, int n)
{
return QCC_showSimpleArray(value, sizeof(double), QCC_showDouble, n);
}
QCC_GenValue *
QCC_genArrayDoubleLR(int len, double from, double to)
{
return QCC_genArrayOfR(len, (QCC_genRawR)QCC_genDoubleAtR, &from, &to, sizeof(double), QCC_showArrayDouble, QCC_freeSimpleValue);
}
QCC_GenValue *
QCC_genArrayDoubleL(int len)
{
return QCC_genArrayOf(len, (QCC_genRaw)QCC_genDoubleAt, sizeof(double), QCC_showArrayDouble, QCC_freeSimpleValue);
}
QCC_GenValue *
QCC_genArrayDouble()
{
return QCC_genArrayOf(50, (QCC_genRaw)QCC_genDoubleAt, sizeof(double), QCC_showArrayDouble, QCC_freeSimpleValue);
}
static char *
QCC_showArrayFloat(void *value, int n)
{
return QCC_showSimpleArray(value, sizeof(float), QCC_showFloat, n);
}
QCC_GenValue *
QCC_genArrayFloatLR(int len, float from, float to)
{
return QCC_genArrayOfR(len, (QCC_genRawR)QCC_genFloatAtR, &from, &to, sizeof(float), QCC_showArrayFloat, QCC_freeSimpleValue);
}
QCC_GenValue *
QCC_genArrayFloatL(int len)
{
return QCC_genArrayOf(len, (QCC_genRaw)QCC_genFloatAt, sizeof(float), QCC_showArrayFloat, QCC_freeSimpleValue);
}
QCC_GenValue *
QCC_genArrayFloat()
{
return QCC_genArrayOf(50, (QCC_genRaw)QCC_genFloatAt, sizeof(float), QCC_showArrayFloat, QCC_freeSimpleValue);
}
static char *
QCC_showArrayBoolean(void *value, int n)
{
return QCC_showSimpleArray(value, sizeof(QCC_Boolean), QCC_showBoolean, n);
}
QCC_GenValue *
QCC_genArrayBooleanL(int len)
{
return QCC_genArrayOf(len, (QCC_genRaw)QCC_genBooleanAt, sizeof(QCC_Boolean), QCC_showArrayBoolean, QCC_freeSimpleValue);
}
QCC_GenValue *
QCC_genArrayBoolean()
{
return QCC_genArrayOf(50, (QCC_genRaw)QCC_genBooleanAt, sizeof(QCC_Boolean), QCC_showArrayBoolean, QCC_freeSimpleValue);
}
static char *
QCC_showArrayChar(void *value, int n)
{
return QCC_showSimpleArray(value, sizeof(char), QCC_showChar, n);
}
QCC_GenValue *
QCC_genArrayCharL(int len)
{
return QCC_genArrayOf(len, (QCC_genRaw)QCC_genCharAt, sizeof(char), QCC_showArrayChar, QCC_freeSimpleValue);
}
QCC_GenValue *
QCC_genArrayChar()
{
return QCC_genArrayOf(50, (QCC_genRaw)QCC_genCharAt, sizeof(char), QCC_showArrayChar, QCC_freeSimpleValue);
}
/***********************************************************************
* Convenience functions
***********************************************************************/
QCC_TestStatus
QCC_not(QCC_TestStatus propStatus)
{
switch (propStatus) {
case QCC_OK:
return QCC_FAIL;
case QCC_FAIL:
return QCC_OK;
case QCC_NOTHING:
return QCC_NOTHING;
default:
return QCC_FAIL;
}
}
QCC_TestStatus
QCC_and(QCC_TestStatus prop1Status, QCC_TestStatus prop2Status)
{
switch (prop1Status) {
case QCC_OK:
return prop2Status;
case QCC_FAIL:
return QCC_FAIL;
case QCC_NOTHING:
return QCC_NOTHING;
default:
return QCC_FAIL;
}
}
QCC_TestStatus
QCC_or(QCC_TestStatus prop1Status, QCC_TestStatus prop2Status)
{
switch (prop1Status) {
case QCC_OK:
return QCC_OK;
case QCC_FAIL:
case QCC_NOTHING:
return prop2Status;
default:
return QCC_FAIL;
}
}
QCC_TestStatus
QCC_xor(QCC_TestStatus prop1Status, QCC_TestStatus prop2Status)
{
switch (prop1Status) {
case QCC_OK:
return QCC_not(prop2Status);
case QCC_FAIL:
return prop2Status;
case QCC_NOTHING:
return QCC_NOTHING;
default:
return QCC_FAIL;
}
}
/***********************************************************************
* Categorization function
***********************************************************************/
void
QCC_freeStamp(QCC_Stamp *stamps)
{
QCC_Stamp *tstamps;
QCC_Stamp *curr;
for (tstamps = stamps; tstamps != NULL;) {
curr = tstamps;
tstamps = tstamps->next;
free(curr->label);
free(curr);
}
}
static void
QCC_insertOrdStamp(QCC_Stamp **stamps, QCC_Stamp *s)
{
QCC_Stamp *new = malloc(sizeof(QCC_Stamp));
*new = (QCC_Stamp) { .label = strdup(s->label), .n = s->n, .next = NULL };
QCC_Stamp *tstamp = *stamps;
QCC_Stamp **pre = stamps;
while (tstamp) {
if (tstamp->n < new->n) {
break;
} else {
pre = &tstamp->next;
tstamp = tstamp->next;
}
}
*pre = new;
new->next = tstamp;
}
static QCC_Stamp *
QCC_sortStamp(QCC_Stamp *stamps)
{
QCC_Stamp *sortedStamps = NULL;
QCC_Stamp *tstamp;
for (tstamp = stamps; tstamp != NULL; tstamp = tstamp->next) {
QCC_insertOrdStamp(&sortedStamps, tstamp);
}
return sortedStamps;
}
static void
QCC_labelN(QCC_Stamp **stamps, char *label, int n)
{
QCC_Stamp *ptr = *stamps;
QCC_Stamp *pre = NULL;
QCC_Stamp *new;
while (ptr) {
if (strcmp(ptr->label, label) == 0) {
(ptr->n)++;
return;
}
pre = ptr;
ptr = ptr->next;
}
new = malloc(sizeof(QCC_Stamp));
*new = (QCC_Stamp) { .label = strdup(label), .n = 1, .next = NULL };
if (pre) {
pre->next = new;
} else {
*stamps = new;
}
}
void
QCC_label(QCC_Stamp **stamps, char *label)
{
return QCC_labelN(stamps, label, 1);
}
static void
QCC_mergeLabels(QCC_Stamp **dst, QCC_Stamp *src)
{
for (; src != NULL; src = src->next)
QCC_labelN(dst, src->label, src->n);
}
/***********************************************************************
* Testing functions
***********************************************************************/
void
QCC_freeGenValues(QCC_GenValue **arguments, int argumentsN)
{
int i;
for (i = 0; i < argumentsN; i++) {
arguments[i]->free(arguments[i]->value);
free(arguments[i]);
}
if (arguments)
free(arguments);
}
void
QCC_freeResult(QCC_Result *res)
{
QCC_freeStamp(res->stamps);
QCC_freeGenValues(res->arguments, res->argumentsN);
}
QCC_Result
QCC_vforAll(QCC_property prop, int genNum, va_list genP)
{ // QCC_gen *genLst,
QCC_GenValue **vals = NULL;
if (genNum) {
int i;
vals = malloc(sizeof(QCC_GenValue *) * genNum);
for (i = 0; i < genNum; i++) {
QCC_gen gen = va_arg(genP, QCC_gen);
vals[i] = gen();
}
}
QCC_Stamp *stamps = NULL;
QCC_TestStatus status = prop(vals, genNum, &stamps);
return (QCC_Result) { .status = status, .stamps = stamps, .arguments = vals, .argumentsN = genNum };
}
QCC_Result
QCC_forAll(QCC_property prop, int genNum, ...)
{ // QCC_gen *genLst,
va_list genP;
QCC_Result res;
va_start(genP, genNum);
res = QCC_vforAll(prop, genNum, genP);
va_end(genP);
return res;
}
static void
QCC_printStamps(QCC_Stamp *stamps, int n)
{
QCC_Stamp *sortedStamps = QCC_sortStamp(stamps);
QCC_Stamp *tstamps;
for (tstamps = sortedStamps; tstamps != NULL; tstamps = tstamps->next)
printf("%.2f%%\t%s\n", (tstamps->n / (float)n) * 100, tstamps->label);
if (sortedStamps)
QCC_freeStamp(sortedStamps);
}
static void
QCC_printArguments(QCC_GenValue **arguments, int argumentsN)
{
int i;
for (i = 0; i < argumentsN; i++) {
char *s = arguments[i]->show(arguments[i]->value, arguments[i]->n);
printf("%s\n", s);
free(s);
}
}
int
QCC_testForAll(int num, int maxFail, QCC_property prop, int genNum, ...)
{
va_list genP;
int succ = 0;
int fail = 0;
QCC_Result res = { .status = QCC_OK };
QCC_Stamp *stamps = NULL;
while (succ < num && fail < maxFail) {
va_start(genP, genNum);
res = QCC_vforAll(prop, genNum, genP);
va_end(genP);
if (res.status == QCC_FAIL) {
break;
} else {
if (res.status == QCC_OK) {
succ++;
QCC_mergeLabels(&stamps, res.stamps);
} else
fail++;
QCC_freeResult(&res);
}
}
if (succ == num) {
//printf("%d test passed (%d)!\n", succ, fail);
QCC_printStamps(stamps, succ);
if (stamps)
QCC_freeStamp(stamps);
return 0;
} else if (res.status == QCC_FAIL) {
printf("\nFalsifiable after %d test\n", succ + 1);
QCC_printArguments(res.arguments, res.argumentsN);
QCC_freeResult(&res);
if (stamps)
QCC_freeStamp(stamps);
return 1;
} else if (fail >= maxFail) {
printf("\nGave up after %d tests!\n", succ);
QCC_printStamps(stamps, succ);
if (stamps)
QCC_freeStamp(stamps);
return -1;
}
return 0;
}
#pragma GCC diagnostic pop

284
test/qc.h Normal file
View file

@ -0,0 +1,284 @@
/********************************************************************
* Copyright (c) 2014, Andrea Zito
* All rights reserved.
*
* License: BSD3
********************************************************************/
#ifndef QUICKCHECK4C_C
#define QUICKCHECK4C_C
#include <stdarg.h>
#include <stdlib.h>
/* Default ranges used for type generation */
#define QCC_LONG_FROM -RAND_MAX / 2
#define QCC_LONG_TO RAND_MAX / 2
#define QCC_INT_FROM -RAND_MAX / 2
#define QCC_INT_TO RAND_MAX / 2
#define QCC_DOUBLE_FROM ((double)-(RAND_MAX / 2))
#define QCC_DOUBLE_TO ((double)(RAND_MAX / 2))
#define QCC_FLOAT_FROM ((float)-(RAND_MAX / 2))
#define QCC_FLOAT_TO ((float)(RAND_MAX / 2))
/**
* Explicit definition of a boolean type compatible with
* normal C boolean interpretation.
*/
typedef enum { QCC_TRUE = 1, QCC_FALSE = 0 } QCC_Boolean;
/**
* Enumerator defining the evaluation of a property.
* QCC_OK represent a satisfied property.
* QCC_FAIL represent a falsified property.
* QCC_NOTHING indicates a property that didn't fail
* but should NOT be considered in the successes count or
* categorization (used by QCC_IMPLY)
*/
typedef enum { QCC_OK = 1, QCC_FAIL = 0, QCC_NOTHING = -1 } QCC_TestStatus;
/**
* Convenince macro to extract generated values inside a property.
* Es: int a = *QCC_getValue(vals, 0, int*)
*
* @param vals A QCC_GenValue array
* @param idx Index of the value to extract
* @param type Type of the generated value
*/
#define QCC_getValue(vals, idx, type) ((type)vals[idx]->value)
/**
* Signature of function used to represent values in a human readable
* fashion.
* Calling this function allocates memory that must be freed by the caller.
*
* @param value Pointer to the value to show
* @param len Lenght of the data (used to keep track of array lengths)
* @return String representation of the value.
*/
typedef char *(*QCC_showValue)(void *value, int len);
/**
* Signature of function used to free generated values.
*
* @param value Pointer to the value to free
*/
typedef void (*QCC_freeValue)(void *value);
/**
* Structure defining a generated value.
* It specifies the value itself (alongside its length in case
* of arrays), a function pointer for getting a human readable
* string representation of the value and a function pointer to
* free the memory allocated for the value.
*
* @param value Pointer to the memory allocated for the value
* @param n Length of the value (1 for simple types, length for arrays)
* @param show Function to get string representation of the value
* @param free Function to free the memory of the value
*/
typedef struct QCC_GenValue {
void *value;
int n;
QCC_showValue show;
QCC_freeValue free;
} QCC_GenValue;
/**
* Opaque structure used to categorize test instances.
* See QCC_property and QCC_label.
*/
typedef struct QCC_Stamp QCC_Stamp;
/**
* Signature of functions used to generate random values
*/
typedef QCC_GenValue *(*QCC_gen)();
/**
* Signature of property functions.
*
* @param vals Array of generated random values
* @param len Number of generated values
* @param stamp Used to categorize test instances via QCC_label
*/
typedef QCC_TestStatus (*QCC_property)(QCC_GenValue **vals, int len, QCC_Stamp **stamps);
/**
* Implements logic implication.
* The implication is satisfied if both the precondition and the property are
* satisfied.
* In case the precondition is not satisfied the special QCC_NOTHING value is
* returned, indicating that the current test should not be considered as a
* success.
*
* @param precondition A boolean condition
* @param property A QCC_property to evaluate iff the precondition is satisfied
* @return QCC_TestStatus
*/
#define QCC_imply(precondition, property) precondition ? property : QCC_NOTHING
/**
* Convenience function to express property conjunction.
* The function returns:
* - QCC_OK iff prop1Status == prop2Status == QCC_OK
* - QCC_FAIL iff prop1Status == QCC_FAIL || prop2Status == QCC_FAIL
* - QCC_NOTHING iff prop1Status != QCC_FAIL && prop2Status != QCC_FAIL &&
* (prop1Status == QCC_NOTHING || prop2Status == QCC_NOTHING)
*
* @param prop1Status The evaluation status of property 1
* @param prop2Status The evaluation status of property 2
* @return The evaluation status of the conjunction of property 1 and property 2
*/
QCC_TestStatus QCC_and(QCC_TestStatus prop1Status, QCC_TestStatus prop2Status);
/**
* Convenience function to express property disjunction.
* The function returns:
* - QCC_OK iff prop1Status == QCC_OK || prop2Status == QCC_OK
* - QCC_FAIL iff prop1Status == QCC_FAIL && prop2Status == QCC_FAIL
* - QCC_NOTHING iff prop1Status != QCC_OK && prop2Status != QCC_OK &&
* (prop1Status == QCC_NOTHING || prop2Status == QCC_NOTHING)
*
* @param prop1Status The evaluation status of property 1
* @param prop2Status The evaluation status of property 2
* @return The evaluation status of the disjunction of property 1 and property 2
*/
QCC_TestStatus QCC_or(QCC_TestStatus prop1Status, QCC_TestStatus prop2Status);
/**
* Convenience function to express property exclusive disjunction.
* The function returns:
* - QCC_OK iff (prop1Status == QCC_OK && prop2Status == QCC_FAIL) ||
* (prop1Status == QCC_FAIL && prop2Status == QCC_OK)
* - QCC_FAIL iff prop1Status == prop2Status != QCC_NOTHING
* - QCC_NOTHING iff prop1Status == QCC_NOTHING || prop2Status = QCC_NOTHING
*
* @param prop1Status The evaluation status of property 1
* @param prop2Status The evaluation status of property 2
* @return The evaluation status of the exclusive disjunction of property 1 and
* property 2
*/
QCC_TestStatus QCC_xor(QCC_TestStatus prop1Status, QCC_TestStatus prop2Status);
/**
* Convenience function to express property negation.
* The function returns:
* - QCC_OK iff propStatus == QCC_FAIL
* - QCC_FAIL iff propStatus == QCC_OK
* - QCC_NOTHING iff propStatus == QCC_NOTHING
*/
QCC_TestStatus QCC_not(QCC_TestStatus propStatus);
/**
* Initialize the random generator using a specific seed.
*
* @param seed The seed to use or 0 to automatically select seed
*/
void QCC_init(int seed);
/**
* Adds a label to the test stamps.
*
* @param stamps Stamps associated to the current test
* @param label Label to add to the test stamps
*/
void QCC_label(QCC_Stamp **stamps, char *label);
/**
* Test a property for num times allowing at most maxFail unsuccesful
* argument generation.
*
* If all num test succede, the function outputs a success string and
* return 0. If argument generation fails maxFail time before a success
* string is printed reporting the number of success test cases and return -1.
*
* In both cases any label label gathered during test evaluation is printed
* alongside its distribution.
*
* If a set of arguments falsifying the property is found, the testing is
* interrupted immediately and a failure string is printed alongside the
* set of arguments which caused the failure.
*
* @parm num Number of successful test to perform
* @parm maxFail Maximum number of unsuccessful argument generation
* (i.e. unsatisfied implication precondition)
* @parm prop Property to test
* @parm genNum Number of generators specified as vararg
* @param ... genNum QCC_gen function to use as generators
* @return 0 (all num test passed),
* -1 (gave up after maxFail unsuccessful arguments generation),
* 1 (property falsified)
*/
int QCC_testForAll(int num, int maxFail, QCC_property prop, int genNum, ...);
/*************************************************************
* Helper function for generator definitions
*************************************************************/
/**
* Initialize a QCC_GenValue using the specified parameters
*
* @param value Raw generated balue
* @param n Length of the value in case of an array (1 should be used otherwise)
* @param show Pointer to a QCC_ShowValue function to use for displaying the raw
* value
* @param free Pointer to a QCC_FreeValue function to use for free the raw value
* memory
* @return Initialized QCC_GenValue
*/
QCC_GenValue *QCC_initGenValue(void *value, int n, QCC_showValue show, QCC_freeValue free);
/*************************************************************
* Simple types generators
*************************************************************/
QCC_GenValue *QCC_genLong();
QCC_GenValue *QCC_genLongR(long from, long to);
QCC_GenValue *QCC_genInt();
QCC_GenValue *QCC_genIntR(int from, int to);
QCC_GenValue *QCC_genDouble();
QCC_GenValue *QCC_genDoubleR(double from, double to);
QCC_GenValue *QCC_genFloat();
QCC_GenValue *QCC_genFloatR(float from, float to);
QCC_GenValue *QCC_genBoolean();
QCC_GenValue *QCC_genChar();
/*************************************************************
* Array types generators
*************************************************************/
QCC_GenValue *QCC_genString();
QCC_GenValue *QCC_genStringL(int len);
QCC_GenValue *QCC_genArrayByte();
QCC_GenValue *QCC_genArrayByteL(int len);
QCC_GenValue *QCC_genArrayByteLR(int len, long from, long to);
QCC_GenValue *QCC_genArrayLong();
QCC_GenValue *QCC_genArrayLongL(int len);
QCC_GenValue *QCC_genArrayLongLR(int len, long from, long to);
QCC_GenValue *QCC_genArrayInt();
QCC_GenValue *QCC_genArrayIntL(int len);
QCC_GenValue *QCC_genArrayIntLR(int len, int from, int to);
QCC_GenValue *QCC_genArrayDouble();
QCC_GenValue *QCC_genArrayDoubleL(int len);
QCC_GenValue *QCC_genArrayDoubleLR(int len, double from, double to);
QCC_GenValue *QCC_genArrayFloat();
QCC_GenValue *QCC_genArrayFloatL(int len);
QCC_GenValue *QCC_genArrayFloatLR(int len, float from, float to);
QCC_GenValue *QCC_genArrayBoolean();
QCC_GenValue *QCC_genArrayBooleanL(int len);
QCC_GenValue *QCC_genArrayChar();
QCC_GenValue *QCC_genArrayCharL(int len);
QCC_GenValue *QCC_genArrayString();
QCC_GenValue *QCC_genArrayStringL(int len, int strLen);
#endif

View file

@ -10,10 +10,10 @@
#include <time.h>
#include <unistd.h>
#include "../include/common.h"
#include "../include/roaring.h"
#include "../include/sparsemap.h"
#include "../include/tdigest.h"
#include <common.h>
#include <roaring.h>
#include <sparsemap.h>
#include <tdigest.h>
#include "midl.c"
@ -336,7 +336,7 @@ record_release_span_mutation(FILE *out, pgno_t pg, unsigned len)
}
static void
__scan_record_offsets(sm_idx_t v[], size_t n, void *aux)
__scan_record_offsets(uint32_t v[], size_t n, void *aux)
{
FILE *out = (FILE *)aux;
for (size_t i = 0; i < n; i++) {
@ -377,7 +377,7 @@ _sparsemap_set(sparsemap_t **_map, sparsemap_idx_t idx, bool value)
{
sparsemap_t *map = *_map, *new_map = NULL;
do {
sparsemap_idx_t l = sparsemap_set(map, idx, value);
sparsemap_idx_t l = sparsemap_assign(map, idx, value);
if (l != idx) {
if (errno == ENOSPC) {
size_t capacity = sparsemap_get_capacity(map) + 64;

View file

@ -1,5 +1,5 @@
/*
* smartmap is MIT-licensed, but for this file:
* sparsemap is MIT-licensed, but for this file:
*
* To the extent possible under law, the author(s) of this file have
* waived all copyright and related or neighboring rights to this
@ -10,15 +10,15 @@
#define MUNIT_NO_FORK (1)
#define MUNIT_ENABLE_ASSERT_ALIASES (1)
#include <common.h>
#include <errno.h>
#include <munit.h>
#include <qc.h>
#include <sparsemap.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "../include/sparsemap.h"
#include "common.h"
#include "munit.h"
#define munit_free free
#if defined(_MSC_VER)
@ -27,6 +27,9 @@
#define SELECT_FALSE
char *QCC_showSparsemap(void *value, int len);
char *QCC_showChunk(void *value, int len);
/* !!! Duplicated here for testing purposes. Keep in sync, or suffer. !!! */
struct sparsemap {
size_t m_capacity;
@ -40,21 +43,33 @@ struct user_data {
/* -------------------------- Supporting Functions for Testing */
void
size_t
populate_map_rle(sparsemap_t *map, size_t loc, size_t num, size_t amount)
{
size_t i, len = munit_rand_int_range(1, num) * amount;
for (i = 0; i < len; i++) {
sparsemap_set(map, loc + i);
}
return i;
}
size_t
populate_map(sparsemap_t *map, int size, int max_value)
{
int array[size];
size_t before;
size_t i, before;
setup_test_array(array, size, max_value);
shuffle(array, size);
before = sparsemap_count(map);
for (int i = 0; i < size; i++) {
sparsemap_set(map, array[i], true);
for (i = 0; i < size; i++) {
sparsemap_set(map, array[i]);
bool set = sparsemap_is_set(map, array[i]);
assert_true(set);
}
assert_true(sparsemap_count(map) == before + size);
return i;
}
static void *
@ -88,6 +103,7 @@ test_api_new(const MunitParameter params[], void *data)
assert_ptr_not_null(map);
assert_true(map->m_capacity == 1024);
assert_true(map->m_data_used == sizeof(uint32_t));
assert_true((((uint8_t)map->m_data[0]) & 0x03) == 0x00);
munit_free(map);
@ -205,7 +221,7 @@ test_api_clear(const MunitParameter params[], void *data)
assert_ptr_not_null(map);
sparsemap_set(map, 42, true);
sparsemap_set(map, 42);
assert_true(sparsemap_is_set(map, 42));
sparsemap_clear(map);
assert_false(sparsemap_is_set(map, 42));
@ -314,7 +330,7 @@ test_api_remaining_capacity(const MunitParameter params[], void *data)
int i = 0;
double cap;
do {
sparsemap_set(map, i++, true);
sparsemap_set(map, i++ * 2);
cap = sparsemap_capacity_remaining(map);
} while (cap > 1.0 && errno != ENOSPC);
errno = 0;
@ -327,7 +343,7 @@ test_api_remaining_capacity(const MunitParameter params[], void *data)
i = 0;
do {
int p = munit_rand_int_range(0, 150000);
sparsemap_set(map, p, true);
sparsemap_set(map, p);
i++;
cap = sparsemap_capacity_remaining(map);
} while (cap > 1.0 && errno != ENOSPC);
@ -346,6 +362,7 @@ test_api_get_capacity_setup(const MunitParameter params[], void *user_data)
sparsemap_init(map, buf, 1024);
populate_map(map, 1024, 3 * 1024);
populate_map_rle(map, 3 * 1024, 5, 4096);
return (void *)map;
}
@ -365,7 +382,7 @@ test_api_get_capacity(const MunitParameter params[], void *data)
assert_ptr_not_null(map);
sparsemap_set(map, 42, true);
sparsemap_set(map, 42);
assert_true(sparsemap_is_set(map, 42));
assert_true(sparsemap_get_capacity(map) == 1024);
@ -400,9 +417,16 @@ test_api_is_set(const MunitParameter params[], void *data)
assert_ptr_not_null(map);
sparsemap_set(map, 42, true);
sparsemap_set(map, 42);
assert_true(sparsemap_is_set(map, 42));
sparsemap_clear(map);
size_t n = populate_map_rle(map, 0, 10, 2718);
for (size_t i = 0; i < n; i++) {
assert_true(sparsemap_is_set(map, i));
}
return MUNIT_OK;
}
@ -435,12 +459,12 @@ test_api_set(const MunitParameter params[], void *data)
assert_false(sparsemap_is_set(map, 1));
assert_false(sparsemap_is_set(map, 8192));
sparsemap_set(map, 1, true);
sparsemap_set(map, 8192, true);
sparsemap_set(map, 1);
sparsemap_set(map, 8192);
assert_true(sparsemap_is_set(map, 1));
assert_true(sparsemap_is_set(map, 8192));
sparsemap_set(map, 1, false);
sparsemap_set(map, 8192, false);
sparsemap_unset(map, 1);
sparsemap_unset(map, 8192);
assert_false(sparsemap_is_set(map, 1));
assert_false(sparsemap_is_set(map, 8192));
@ -456,6 +480,7 @@ test_api_get_size_setup(const MunitParameter params[], void *user_data)
sparsemap_init(map, buf, 1024);
populate_map(map, 1024, 3 * 1024);
populate_map_rle(map, 3 * 1024, 5, 4096);
return (void *)map;
}
@ -512,18 +537,22 @@ test_api_count(const MunitParameter params[], void *data)
assert_true(sparsemap_count(map) == 1024);
sparsemap_clear(map);
sparsemap_set(map, 0, true);
sparsemap_set(map, 0);
assert_true(sparsemap_count(map) == 1);
sparsemap_set(map, 8675309, true);
sparsemap_set(map, 8675309);
assert_true(sparsemap_count(map) == 2);
sparsemap_clear(map);
for (int i = 0; i < 512; i++) {
sparsemap_set(map, i + 13, true);
sparsemap_set(map, i + 13);
}
assert_true(sparsemap_count(map) == 512);
sparsemap_clear(map);
size_t n = populate_map_rle(map, 3 * 1024, 7, 4001);
assert_true(sparsemap_count(map) == n);
sparsemap_clear(map);
assert_true(sparsemap_count(map) == 0);
@ -550,7 +579,7 @@ test_api_get_data(const MunitParameter params[], void *data)
}
static void *
test_api_get_starting_offset_setup(const MunitParameter params[], void *user_data)
test_api_get_start_offset_setup(const MunitParameter params[], void *user_data)
{
uint8_t *buf = munit_calloc(1024, sizeof(uint8_t));
assert_ptr_not_null(buf);
@ -561,7 +590,7 @@ test_api_get_starting_offset_setup(const MunitParameter params[], void *user_dat
return (void *)map;
}
static void
test_api_get_starting_offset_tear_down(void *fixture)
test_api_get_start_offset_tear_down(void *fixture)
{
sparsemap_t *map = (sparsemap_t *)fixture;
assert_ptr_not_null(map->m_data);
@ -569,45 +598,45 @@ test_api_get_starting_offset_tear_down(void *fixture)
test_api_tear_down(fixture);
}
static MunitResult
test_api_get_starting_offset(const MunitParameter params[], void *data)
test_api_get_start_offset(const MunitParameter params[], void *data)
{
sparsemap_t *map = (sparsemap_t *)data;
(void)params;
assert_ptr_not_null(map);
sparsemap_set(map, 0, true);
sparsemap_set(map, 0);
assert_true(sparsemap_get_starting_offset(map) == 0);
sparsemap_clear(map);
sparsemap_set(map, 1, true);
sparsemap_set(map, 1);
assert_true(sparsemap_get_starting_offset(map) == 1);
sparsemap_clear(map);
sparsemap_set(map, 1025, true);
sparsemap_set(map, 1025);
assert_true(sparsemap_get_starting_offset(map) == 1025);
sparsemap_clear(map);
for (int i = 0; i < 1000; i++) {
sparsemap_set(map, i, true);
sparsemap_set(map, i);
}
assert_true(sparsemap_get_starting_offset(map) == 0);
sparsemap_clear(map);
for (int i = 0; i < 1000; i++) {
sparsemap_set(map, i + 1024, true);
sparsemap_set(map, i + 1024);
}
assert_true(sparsemap_get_starting_offset(map) == 1024);
sparsemap_clear(map);
sparsemap_set(map, 13012, true);
sparsemap_set(map, 13012);
assert_true(sparsemap_get_starting_offset(map) == 13012);
return MUNIT_OK;
}
static void *
test_api_get_ending_offset_setup(const MunitParameter params[], void *user_data)
test_api_get_end_offset_setup(const MunitParameter params[], void *user_data)
{
uint8_t *buf = munit_calloc(1024, sizeof(uint8_t));
assert_ptr_not_null(buf);
@ -618,7 +647,7 @@ test_api_get_ending_offset_setup(const MunitParameter params[], void *user_data)
return (void *)map;
}
static void
test_api_get_ending_offset_tear_down(void *fixture)
test_api_get_end_offset_tear_down(void *fixture)
{
sparsemap_t *map = (sparsemap_t *)fixture;
assert_ptr_not_null(map->m_data);
@ -626,34 +655,44 @@ test_api_get_ending_offset_tear_down(void *fixture)
test_api_tear_down(fixture);
}
static MunitResult
test_api_get_ending_offset(const MunitParameter params[], void *data)
test_api_get_end_offset(const MunitParameter params[], void *data)
{
sparsemap_t *map = (sparsemap_t *)data;
(void)params;
assert_ptr_not_null(map);
sparsemap_set(map, 0, true);
sparsemap_set(map, 0);
assert_true(sparsemap_get_ending_offset(map) == 0);
sparsemap_clear(map);
sparsemap_set(map, 0, true);
sparsemap_set(map, 1, true);
sparsemap_set(map, 0);
sparsemap_set(map, 1);
assert_true(sparsemap_get_ending_offset(map) == 1);
sparsemap_clear(map);
sparsemap_set(map, 0, true);
sparsemap_set(map, 67, true);
sparsemap_set(map, 1002, true);
sparsemap_set(map, 3087, true);
sparsemap_set(map, 13012, true);
sparsemap_set(map, 0);
sparsemap_set(map, 67);
sparsemap_set(map, 1002);
sparsemap_set(map, 3087);
sparsemap_set(map, 13012);
assert_true(sparsemap_get_ending_offset(map) == 13012);
sparsemap_clear(map);
size_t n = populate_map_rle(map, 13012, 10, 2718);
size_t exp = n + 13012 - 1;
size_t eoff = sparsemap_get_ending_offset(map);
assert_true(sparsemap_get_ending_offset(map) == 13012 + n - 1);
// fprintf(stdout, "\n%s\n", QCC_showSparsemap(map, 0));
sparsemap_set(map, 13012 + n + 100);
// fprintf(stdout, "\n%s\n", QCC_showSparsemap(map, 0));
assert_true(sparsemap_get_ending_offset(map) == 13112 + n);
return MUNIT_OK;
}
static void *
test_api_get_starting_offset_rolling_setup(const MunitParameter params[], void *user_data)
test_api_get_start_offset_roll_setup(const MunitParameter params[], void *user_data)
{
(void)params;
(void)user_data;
@ -662,24 +701,27 @@ test_api_get_starting_offset_rolling_setup(const MunitParameter params[], void *
return (void *)map;
}
static void
test_api_get_starting_offset_rolling_tear_down(void *fixture)
test_api_get_start_offset_roll_tear_down(void *fixture)
{
sparsemap_t *map = (sparsemap_t *)fixture;
assert_ptr_not_null(map);
munit_free(map);
}
static MunitResult
test_api_get_starting_offset_rolling(const MunitParameter params[], void *data)
test_api_get_start_offset_roll(const MunitParameter params[], void *data)
{
sparsemap_t *map = (sparsemap_t *)data;
(void)params;
for (sparsemap_idx_t i = 0; i < 10 * 2048; i++) {
sparsemap_set(map, i, true);
sparsemap_set(map, i);
if (i > 2047) {
sparsemap_set(map, i - 2048, false);
sparsemap_unset(map, i - 2048);
// if (sparsemap_get_starting_offset(map) != i - 2047) {
// fprintf(stdout, "\n%s\n", QCC_showSparsemap(map, 0));
// fprintf(stdout, "%ld\t%ld\t%zu\n", i, i - 2047, sparsemap_get_starting_offset(map));
// }
assert_true(sparsemap_get_starting_offset(map) == i - 2047);
// printf("%d\t%d\t%zu\n", i, i - 2047, sparsemap_get_starting_offset(map));
}
}
return MUNIT_OK;
@ -703,7 +745,7 @@ test_api_scan_tear_down(void *fixture)
test_api_tear_down(fixture);
}
void
scan_for_0xfeedfacebadcoffee(sm_idx_t v[], size_t n, void *aux)
scan_for_0xfeedfacebadcoffee(uint32_t v[], size_t n, void *aux)
{
size_t bit_pos[] = { 1, 2, 3, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 22, 23, 24, 26, 27, 29, 31, 32, 33, 34, 35, 38, 39, 41, 43, 44, 45, 46, 47, 48, 50, 51,
53, 54, 55, 57, 58, 59, 60, 61, 62, 63 };
@ -747,6 +789,7 @@ test_api_split_tear_down(void *fixture)
static MunitResult
test_api_split(const MunitParameter params[], void *data)
{
sparsemap_idx_t offset;
sparsemap_t *map = (sparsemap_t *)data;
uint8_t buf[1024] = { 0 };
sparsemap_t portion;
@ -756,11 +799,33 @@ test_api_split(const MunitParameter params[], void *data)
sparsemap_init(&portion, buf, 1024);
size_t amt = populate_map_rle(map, 0, 7, 8178);
for (sparsemap_idx_t i = 0; i < amt + 2049; i++) {
sparsemap_clear(&portion);
size_t rank = sparsemap_rank(map, 0, i, true);
offset = sparsemap_split(map, i + 1, &portion);
if (sparsemap_count(map) != rank) {
fprintf(stdout, "exp: %lu\tgot: %lu", rank, sparsemap_count(map));
}
assert_true(sparsemap_count(map) == rank);
if (sparsemap_count(&portion) != amt - rank) {
fprintf(stdout, "exp: %lu\tgot: %lu", amt - rank, sparsemap_count(&portion));
}
assert_true(sparsemap_count(&portion) == amt - rank);
sparsemap_merge(map, &portion);
if (*(uint32_t *)map->m_data != 1) {
fprintf(stdout, "yikes");
}
}
sparsemap_clear(map);
sparsemap_clear(&portion);
for (sparsemap_idx_t off = 0; off < 1024; off++) {
for (sparsemap_idx_t seg = 0; seg < 10 * 1024; seg += 1024) {
for (sparsemap_idx_t i = 0; i < 1024; i++) {
assert_true(sparsemap_set(map, i + seg, true) == i + seg);
assert_true(sparsemap_set(map, i + seg) == i + seg);
}
for (sparsemap_idx_t i = 0; i < 1024; i++) {
assert_true(sparsemap_is_set(map, i + seg));
@ -784,20 +849,20 @@ test_api_split(const MunitParameter params[], void *data)
}
for (sparsemap_idx_t i = 0; i < 100; i++) {
assert_true(sparsemap_set(map, i, true) == i);
assert_true(sparsemap_set(map, i) == i);
}
for (sparsemap_idx_t i = 0; i < 100; i++) {
assert_true(sparsemap_is_set(map, i));
assert_false(sparsemap_is_set(&portion, i));
}
sparsemap_idx_t offset = sparsemap_split(map, SPARSEMAP_IDX_MAX, &portion);
offset = sparsemap_split(map, SPARSEMAP_IDX_MAX, &portion);
for (size_t i = 0; i < offset; i++) {
for (sparsemap_idx_t i = 0; i < offset; i++) {
assert_true(sparsemap_is_set(map, i));
assert_false(sparsemap_is_set(&portion, i));
}
for (int i = offset; i < 100; i++) {
for (sparsemap_idx_t i = offset + 1; i < sparsemap_get_ending_offset(&portion); i++) {
assert_false(sparsemap_is_set(map, i));
assert_true(sparsemap_is_set(&portion, i));
}
@ -807,10 +872,14 @@ test_api_split(const MunitParameter params[], void *data)
sparsemap_init(&portion, buf, 1024);
for (sparsemap_idx_t i = 0; i < 13; i++) {
assert_true(sparsemap_set(map, i + 24, true) == i + 24);
assert_true(sparsemap_set(map, i + 24) == i + 24);
}
offset = sparsemap_split(map, SPARSEMAP_IDX_MAX, &portion);
assert_true(sparsemap_get_ending_offset(map) < offset);
assert_true(sparsemap_get_starting_offset(&portion) >= offset);
assert_true(sparsemap_count(map) == 6);
assert_true(sparsemap_count(&portion) == 7);
for (sparsemap_idx_t i = 0; i < offset - 24; i++) {
assert_true(sparsemap_is_set(map, i + 24));
@ -854,7 +923,7 @@ test_api_merge(const MunitParameter params[], void *data)
sparsemap_merge(map, other);
// Merge a single set bit in the first chunk into the empty map.
sparsemap_set(other, 0, true);
sparsemap_set(other, 0);
sparsemap_merge(map, other);
assert_true(sparsemap_is_set(other, 0));
assert_true(sparsemap_is_set(map, 0));
@ -862,32 +931,32 @@ test_api_merge(const MunitParameter params[], void *data)
sparsemap_clear(other);
// Merge two maps with the same single bit set.
sparsemap_set(map, 0, true);
sparsemap_set(other, 0, true);
sparsemap_set(map, 0);
sparsemap_set(other, 0);
sparsemap_merge(map, other);
assert_true(sparsemap_is_set(map, 0));
sparsemap_clear(map);
sparsemap_clear(other);
// Merge an empty map with one that has the first bit set.
sparsemap_set(map, 0, true);
sparsemap_set(map, 0);
sparsemap_merge(map, other);
assert_true(sparsemap_is_set(map, 0));
sparsemap_clear(map);
sparsemap_clear(other);
sparsemap_set(other, 2049, true);
sparsemap_set(other, 2049);
sparsemap_merge(map, other);
assert_true(sparsemap_is_set(map, 2049));
sparsemap_clear(map);
sparsemap_clear(other);
sparsemap_set(other, 1, true);
sparsemap_set(other, 2049, true);
sparsemap_set(map, 2050, true);
sparsemap_set(other, 4097, true);
sparsemap_set(map, 6113, true);
sparsemap_set(other, 8193, true);
sparsemap_set(other, 1);
sparsemap_set(other, 2049);
sparsemap_set(map, 2050);
sparsemap_set(other, 4097);
sparsemap_set(map, 6113);
sparsemap_set(other, 8193);
sparsemap_merge(map, other);
assert_true(sparsemap_is_set(map, 1));
assert_true(sparsemap_is_set(map, 2049));
@ -904,11 +973,11 @@ test_api_merge(const MunitParameter params[], void *data)
sparsemap_clear(map);
sparsemap_clear(other);
sparsemap_set(map, 0, true);
sparsemap_set(map, 2048, true);
sparsemap_set(map, 8193, true);
sparsemap_set(map, 0);
sparsemap_set(map, 2048);
sparsemap_set(map, 8193);
for (int i = 2049; i < 4096; i++) {
sparsemap_set(other, i, true);
sparsemap_set(other, i);
}
sparsemap_merge(map, other);
assert(sparsemap_is_set(map, 0));
@ -921,7 +990,7 @@ test_api_merge(const MunitParameter params[], void *data)
sparsemap_clear(other);
for (int i = 2049; i < 4096; i++) {
sparsemap_set(map, i, true);
sparsemap_set(map, i);
}
sparsemap_split(map, 2051, other);
for (int i = 2049; i < 4096; i++) {
@ -1048,9 +1117,9 @@ test_api_select_neg(const MunitParameter params[], void *data)
assert_ptr_not_null(map);
sparsemap_set(map, 42, true);
sparsemap_set(map, 420, true);
sparsemap_set(map, 4200, true);
sparsemap_set(map, 42);
sparsemap_set(map, 420);
sparsemap_set(map, 4200);
f = sparsemap_select(map, 0, false);
assert_true(f == 0);
@ -1095,7 +1164,7 @@ test_api_rank_true(const MunitParameter params[], void *data)
assert_ptr_not_null(map);
for (int i = 0; i < 10; i++) {
sparsemap_set(map, i, true);
sparsemap_set(map, i);
}
/* rank() is also 0-based, for consistency (and confusion sake); consider the
range as [start, end] of [0, 9] counts the bits set in the first 10
@ -1109,14 +1178,23 @@ test_api_rank_true(const MunitParameter params[], void *data)
sparsemap_clear(map);
for (int i = 0; i < 10000; i++) {
sparsemap_set(map, i, true);
sparsemap_set(map, i);
}
// TODO: separate test for slicing a run within the chunk size of the end of the run
// sparsemap_unset(map, 9990);
// sparsemap_set(map, 9990);
sparsemap_idx_t hole = 4999;
sparsemap_set(map, hole, false);
sparsemap_unset(map, hole);
for (size_t i = 0; i < 10000; i++) {
for (size_t j = i; j < 10000; j++) {
size_t amt = (i > j) ? 0 : j - i + 1 - ((hole >= i && j >= hole) ? 1 : 0);
size_t r = sparsemap_rank(map, i, j, true);
// if (r != amt) {
// fprintf(stdout, "\n%s\n", QCC_showSparsemap(map, 0));
// sparsemap_rank(map, i, j, true);
// }
assert_true(r == amt);
}
}
@ -1146,7 +1224,7 @@ test_api_rank_false_tear_down(void *fixture)
static MunitResult
test_api_rank_false(const MunitParameter params[], void *data)
{
int r;
size_t r;
sparsemap_t *map = (sparsemap_t *)data;
(void)params;
@ -1162,18 +1240,36 @@ test_api_rank_false(const MunitParameter params[], void *data)
// One chunk means not so empty now!
sparsemap_idx_t hole = 4999;
sparsemap_set(map, hole, true);
sparsemap_set(map, hole);
for (size_t i = 0; i < 10000; i++) {
for (size_t j = i; j < 10000; j++) {
int amt = (i > j) ? 0 : j - i + 1 - ((hole >= i && j >= hole) ? 1 : 0);
size_t amt = (i > j) ? 0 : j - i + 1 - ((hole >= i && j >= hole) ? 1 : 0);
r = sparsemap_rank(map, i, j, false);
assert_true(r == amt);
}
}
// RLE
for (size_t i = 0; i < 10000; i++) {
sparsemap_set(map, i);
}
r = sparsemap_rank(map, 9990, 10010, false);
// if (r != 10) {
// fprintf(stdout, "\n%s\n", QCC_showSparsemap(map, 0));
// sparsemap_rank(map, 9990, 10010, true);
// }
assert_true(r == 10);
r = sparsemap_rank(map, 9990, 4294967295, false);
// if (r != 4294957295) {
// fprintf(stdout, "\n%s\n", QCC_showSparsemap(map, 0));
// sparsemap_rank(map, 9990, 4294967295, true);
// }
assert_true(r == 4294957295);
sparsemap_clear(map);
sparsemap_set(map, 1, true);
sparsemap_set(map, 11, true);
sparsemap_set(map, 1);
sparsemap_set(map, 11);
r = sparsemap_rank(map, 0, 11, false);
assert_true(r == 10);
@ -1246,9 +1342,9 @@ static MunitTest api_test_suite[] = {
{ (char *)"/get_size", test_api_get_size, test_api_get_size_setup, test_api_get_size_tear_down, MUNIT_TEST_OPTION_NONE, NULL },
{ (char *)"/count", test_api_count, test_api_count_setup, test_api_count_tear_down, MUNIT_TEST_OPTION_NONE, NULL },
{ (char *)"/get_data", test_api_get_data, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
{ (char *)"/get_starting_offset", test_api_get_starting_offset, test_api_get_starting_offset_setup, test_api_get_starting_offset_tear_down, MUNIT_TEST_OPTION_NONE, NULL },
{ (char *)"/get_starting_offset/rolling", test_api_get_starting_offset_rolling, test_api_get_starting_offset_rolling_setup, test_api_get_starting_offset_rolling_tear_down, MUNIT_TEST_OPTION_NONE, NULL },
{ (char *)"/get_ending_offset", test_api_get_ending_offset, test_api_get_ending_offset_setup, test_api_get_ending_offset_tear_down, MUNIT_TEST_OPTION_NONE, NULL },
{ (char *)"/get_start_offset", test_api_get_start_offset, test_api_get_start_offset_setup, test_api_get_start_offset_tear_down, MUNIT_TEST_OPTION_NONE, NULL },
{ (char *)"/get_start_offset/roll", test_api_get_start_offset_roll, test_api_get_start_offset_roll_setup, test_api_get_start_offset_roll_tear_down, MUNIT_TEST_OPTION_NONE, NULL },
{ (char *)"/get_end_offset", test_api_get_end_offset, test_api_get_end_offset_setup, test_api_get_end_offset_tear_down, MUNIT_TEST_OPTION_NONE, NULL },
{ (char *)"/scan", test_api_scan, test_api_scan_setup, test_api_scan_tear_down, MUNIT_TEST_OPTION_NONE, NULL },
{ (char *)"/split", test_api_split, test_api_split_setup, test_api_split_tear_down, MUNIT_TEST_OPTION_NONE, NULL },
{ (char *)"/merge", test_api_merge, test_api_merge_setup, test_api_merge_tear_down, MUNIT_TEST_OPTION_NONE, NULL },
@ -1266,6 +1362,61 @@ static MunitTest api_test_suite[] = {
};
// clang-format on
/* -------------------------- Quickcheck, Property Based Tests */
extern QCC_GenValue *QCC_genChunk();
extern QCC_GenValue *QCC_genSparsemap();
extern QCC_TestStatus _tst_chunk_calc_vector_size_equality(QCC_GenValue **vals, int len, QCC_Stamp **stamp);
extern QCC_TestStatus _tst_chunk_get_position(QCC_GenValue **vals, int len, QCC_Stamp **stamp);
extern QCC_TestStatus _tst_chunk_get_capacity(QCC_GenValue **vals, int len, QCC_Stamp **stamp);
extern QCC_TestStatus _tst_get_chunk_offset(QCC_GenValue **vals, int len, QCC_Stamp **stamp);
static MunitResult
qc__sm_chunk_calc_vector_size(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
return QCC_testForAll(1000, 1000, _tst_chunk_calc_vector_size_equality, 1, QCC_genInt);
}
static MunitResult
qc__sm_chunk_get_position(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
return QCC_testForAll(100, 1000, _tst_chunk_get_position, 1, QCC_genChunk);
}
static MunitResult
qc__sm_chunk_get_capacity(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
return QCC_testForAll(100, 1000, _tst_chunk_get_capacity, 1, QCC_genChunk);
}
static MunitResult
qc__sm_get_chunk_offset(const MunitParameter params[], void *data)
{
(void)params;
(void)data;
return QCC_testForAll(100, 1000, _tst_get_chunk_offset, 2, QCC_genInt, QCC_genSparsemap);
}
// clang-format off
static MunitTest qc_test_suite[] = {
{ (char *)"/__sm_chunk_calc_vector_size", qc__sm_chunk_calc_vector_size, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
{ (char *)"/__sm_chunk_get_position", qc__sm_chunk_get_position, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
{ (char *)"/__sm_chunk_get_capacity", qc__sm_chunk_get_capacity, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
{ (char *)"/__sm_get_chunk_offset", qc__sm_get_chunk_offset, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL },
{ NULL, NULL, NULL, NULL, MUNIT_TEST_OPTION_NONE, NULL }
};
// clang-format off
/* -------------------------- Scale Tests */
static void *
@ -1407,7 +1558,7 @@ test_scale_alternating(const MunitParameter params[], void *data)
for (sparsemap_idx_t i = 0; i < (1000 * 8192); i++) {
if (i % 2) {
if (sparsemap_set(map, i, true) != i) {
if (sparsemap_set(map, i) != i) {
// printf("%zu\n", i);
break;
}
@ -1463,7 +1614,7 @@ test_scale_spans_come_spans_go(const MunitParameter params[], void *data)
assert_true(sparsemap_is_set(map, j) == true);
}
for (int j = b; j < s; j++) {
sparsemap_set(map, j, false);
sparsemap_unset(map, j);
}
for (int j = b; j < s; j++) {
assert_true(sparsemap_is_set(map, j) == false);
@ -1515,7 +1666,7 @@ test_scale_best_case(const MunitParameter params[], void *data)
for (int i = 0; i < 172032; i++) {
/* ANSI esc code to clear line, carrage return, then print on the same line */
// printf("\033[2K\r%d", i);
sparsemap_set(map, i, true);
sparsemap_set(map, i);
}
return MUNIT_OK;
@ -1559,7 +1710,7 @@ test_scale_worst_case(const MunitParameter params[], void *data)
for (int i = 0; i < 7744; i += 2) {
/* ANSI esc code to clear line, carrage return, then print on the same line */
// printf("\033[2K\r%d", i);
sparsemap_set(map, i, true);
sparsemap_set(map, i);
}
return MUNIT_OK;
@ -1680,6 +1831,7 @@ static MunitTest perf_test_suite[] = {
// clang-format off
static MunitSuite other_test_suite[] = {
{ "/api", api_test_suite, NULL, 1, MUNIT_SUITE_OPTION_NONE },
{ "/qc", qc_test_suite, NULL, 1, MUNIT_SUITE_OPTION_NONE },
{ "/perf", perf_test_suite, NULL, 1, MUNIT_SUITE_OPTION_NONE },
{ "/scale", scale_test_suite, NULL, 1, MUNIT_SUITE_OPTION_NONE },
{ NULL, NULL, NULL, 0, MUNIT_SUITE_OPTION_NONE } };
@ -1691,7 +1843,7 @@ static MunitTest sparsemap_test_suite[] = {
};
// clang-format on
static const MunitSuite main_test_suite = { (char *)"/sparsemap", sparsemap_test_suite, other_test_suite, 1, MUNIT_SUITE_OPTION_NONE };
static const MunitSuite main_test_suite = { (char *)"", sparsemap_test_suite, other_test_suite, 1, MUNIT_SUITE_OPTION_NONE };
int
main(int argc, char *argv[MUNIT_ARRAY_PARAM(argc + 1)])
@ -1702,6 +1854,8 @@ main(int argc, char *argv[MUNIT_ARRAY_PARAM(argc + 1)])
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
QCC_init(0);
return munit_suite_main(&main_test_suite, (void *)&info, argc, argv);
}