Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
8a39c40
WIP
celonymire Oct 1, 2025
29d6539
Remove unused import
celonymire Oct 1, 2025
7222bf2
Also print out any stderr in test runner
celonymire Oct 1, 2025
5d0599e
Test `Treap`
celonymire Oct 1, 2025
83958a4
`segment_tree_lazy`: Add `identity`
celonymire Oct 2, 2025
548dcb4
`segment_tree_lazy`: Fix bad constructor signature with duplicate same
celonymire Oct 2, 2025
49c23a6
Add `segment_tree_lazy` test
celonymire Oct 2, 2025
74ed016
Fix wrong analysis solution banner
celonymire Oct 2, 2025
dd2a015
Add `segment_tree` test
celonymire Oct 2, 2025
09156a9
`mo_array`: Fix compile error from name shadowing
celonymire Oct 2, 2025
a807e53
Add `treap` test
celonymire Oct 2, 2025
fdac407
Update solution banners to point to their source
celonymire Oct 2, 2025
a1695f7
`mo_array`: Qualify forward call with `std::`
celonymire Oct 2, 2025
193b584
`aho_corasick`: Make it a function that returns the automaton graph
celonymire Oct 2, 2025
0583f81
Add `aho_corasick` test
celonymire Oct 2, 2025
1ae597c
Remove debug print statement
celonymire Oct 2, 2025
c8ec9f2
Add `rolling_hash` test
celonymire Oct 2, 2025
d5da2af
`fenwick`: Add vector constructor
celonymire Oct 2, 2025
3b3bb95
Add `fenwick` test
celonymire Oct 2, 2025
cb9ed56
`sparse_table`: Update to fit its practical purpose of constant time
celonymire Oct 2, 2025
8497195
Add `sparse_table` test
celonymire Oct 2, 2025
1a6e693
`combinatorics`: Fix include to `modint`
celonymire Oct 3, 2025
39be7bb
`modint`: Cleanups and make value access explicit
celonymire Oct 3, 2025
22cfd3a
`combinatorics`: Fixed missed remaining `modnum` and other updates.
celonymire Oct 3, 2025
3f5b527
Add `combinatorics` test.
celonymire Oct 3, 2025
4faf67b
`modint`: Add explicit int conversion operator back
celonymire Oct 3, 2025
ccea382
Merge branch 'master' into ci
celonymire Oct 3, 2025
e79ad42
Update `combinatorics` test generated bounds to include 0
celonymire Oct 3, 2025
6fdb03f
Add `strongly_connected_components` test
celonymire Oct 3, 2025
b0b3395
`union_find`: Add a getter for number of components
celonymire Oct 3, 2025
06b34fb
Add `union_find` test
celonymire Oct 3, 2025
941dc36
Rename folder to `data_structure`
celonymire Oct 3, 2025
af1adb6
Add `lowest_common_ancestor` test
celonymire Oct 3, 2025
f252efa
`modint`: Add explicit bool conversion operator
celonymire Oct 3, 2025
cc5f4c1
Add `matrix` test
celonymire Oct 3, 2025
f0a6133
Update random.py
celonymire Oct 3, 2025
4437207
Add `bridges` test
celonymire Oct 3, 2025
d931fef
Add testing workflow
celonymire Oct 3, 2025
62145d3
Merge branch 'master' into ci
celonymire Oct 3, 2025
c163eec
Downgrade required CMake version to 3.30 for CI
celonymire Oct 3, 2025
1a2d6a8
Output failed tests on failure
celonymire Oct 3, 2025
0832a5a
Try an alternative ctest invocation
celonymire Oct 3, 2025
ab5bdfd
Add missing `__init__.py` files
celonymire Oct 3, 2025
c5fb927
Renamed CI task from "build" to "test"
celonymire Oct 3, 2025
09d31a0
Go back to use raw CTest command
celonymire Oct 3, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
on:
push:
branches:
- main
pull_request:

jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Configure tests
run: cmake . -B build

- name: Build tests
run: cmake --build build --target all --parallel

- name: Run tests
run: ctest --test-dir build --output-on-failure -j
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

!library/
!leetcode/
!test/
!.github/
!.clang-format
!ruff.toml
!README.md
!CMakeLists.txt
!ac-library
8 changes: 8 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.30)
project(competitive-programming CXX)

include(CTest)

if(BUILD_TESTING)
add_subdirectory("test")
endif()
1 change: 1 addition & 0 deletions library/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
**/.cache
4 changes: 4 additions & 0 deletions test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
**/__pycache__
build/
.cache/
compile_commands.json
41 changes: 41 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
find_package(Python COMPONENTS Interpreter REQUIRED)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

include_directories("../library/cpp")

function(add_library_test name)
add_executable(${name}_program "program/${name}.cpp")
add_executable(${name}_solution "solution/${name}.cpp")

add_test(NAME ${name}
COMMAND ${Python_EXECUTABLE} -m test.checker.${name} -p $<TARGET_FILE:${name}_program> -s $<TARGET_FILE:${name}_solution>
WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}
)
endfunction()

# data_structure
add_library_test("union_find")

# graph
add_library_test("bridges")
add_library_test("lowest_common_ancestor")
add_library_test("strongly_connected_components")

# math
add_library_test("combinatorics")
add_library_test("matrix")

# range_query
add_library_test("fenwick")
add_library_test("heavy_light_decomposition")
add_library_test("mo_array")
add_library_test("segment_tree")
add_library_test("segment_tree_lazy")
add_library_test("sparse_table")
add_library_test("treap")

# string
add_library_test("aho_corasick")
add_library_test("rolling_hash")
Empty file added test/__init__.py
Empty file.
35 changes: 35 additions & 0 deletions test/checker/aho_corasick.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from test.lib import cli, runner
from random import randint, choices
from string import ascii_lowercase


def main():
args = cli.args.parse_args()
iter = args.iterations
for _ in range(iter):
stdin = []

n = randint(1, 10**5)
s = "".join(choices(ascii_lowercase, k=n))
stdin.append(s)

tot = 5 * 10**5
k_max = randint(1, 5 * 10**5)
patterns = []
for _ in range(k_max):
if tot == 0:
break
m = randint(1, tot)
p = "".join(choices(ascii_lowercase, k=m))
patterns.append(p)
tot -= m

stdin.append(str(len(patterns)))
stdin.extend(patterns)

proc_ans, sol_ans = runner.run(args.program, args.solution, "\n".join(stdin))
assert proc_ans == sol_ans


if __name__ == "__main__":
main()
32 changes: 32 additions & 0 deletions test/checker/bridges.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
from test.lib import cli, runner, random
from random import randint
from itertools import batched


def main():
args = cli.args.parse_args()
iter = args.iterations
for _ in range(iter):
stdin = []

n = randint(1, 10**5)
m = randint(n - 1, min(10**5, n * (n - 1) // 2))
stdin.append(f"{n} {m}")

ed = random.rand_connected_graph_edges(n, m)
for a, b in ed:
stdin.append(f"{a} {b}")

proc_ans, sol_ans = runner.run(args.program, args.solution, "\n".join(stdin))

proc_ans = proc_ans.split()
sol_ans = sol_ans.split()
assert proc_ans[0] == sol_ans[0]

proc_ed = sorted([(min(a, b), max(a, b)) for a, b in batched(proc_ans[1:], 2)])
sol_ed = sorted([(min(a, b), max(a, b)) for a, b in batched(sol_ans[1:], 2)])
assert proc_ed == sol_ed


if __name__ == "__main__":
main()
24 changes: 24 additions & 0 deletions test/checker/combinatorics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from test.lib import cli, runner
from random import randint


def main():
args = cli.args.parse_args()
iter = args.iterations
for _ in range(iter):
stdin = []

n = randint(1, 10**5)
stdin.append(str(n))

for _ in range(n):
a = randint(0, 10**6)
b = randint(0, a)
stdin.append(f"{a} {b}")

proc_ans, sol_ans = runner.run(args.program, args.solution, "\n".join(stdin))
assert proc_ans == sol_ans


if __name__ == "__main__":
main()
34 changes: 34 additions & 0 deletions test/checker/fenwick.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from test.lib import cli, runner
from random import randint


def main():
args = cli.args.parse_args()
iter = args.iterations
for _ in range(iter):
stdin = []

n = randint(1, 2 * 10**5)
q = randint(1, 2 * 10**5)
stdin.append(f"{n} {q}")

x = [randint(1, 10**9) for _ in range(n)]
stdin.append(" ".join(map(str, x)))

for _ in range(q):
type = randint(1, 2)
if type == 1:
k = randint(1, n)
u = randint(1, 10**9)
stdin.append(f"1 {k} {u}")
else:
a = randint(1, n)
b = randint(a, n)
stdin.append(f"2 {a} {b}")

proc_ans, sol_ans = runner.run(args.program, args.solution, "\n".join(stdin))
assert proc_ans == sol_ans


if __name__ == "__main__":
main()
38 changes: 38 additions & 0 deletions test/checker/heavy_light_decomposition.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from test.lib import cli, runner, random
from random import randint


def main():
args = cli.args.parse_args()
iter = args.iterations
for _ in range(iter):
stdin = []

n = randint(1, 2 * 10**5)
q = randint(1, 2 * 10**5)
stdin.append(f"{n} {q}")

v = [str(randint(1, 10**9)) for _ in range(n)]
stdin.append(" ".join(v))

ed = random.rand_tree_edges(n)
for a, b in ed:
stdin.append(f"{a} {b}")

for _ in range(q):
type = randint(1, 2)
if type == 1:
s = randint(1, n)
x = randint(1, 10**9)
stdin.append(f"1 {s} {x}")
else:
a = randint(1, n)
b = randint(1, n)
stdin.append(f"2 {a} {b}")

proc_ans, sol_ans = runner.run(args.program, args.solution, "\n".join(stdin))
assert proc_ans == sol_ans


if __name__ == "__main__":
main()
35 changes: 35 additions & 0 deletions test/checker/lowest_common_ancestor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from test.lib import cli, runner, random
from random import randint
import sys
import resource

sys.setrecursionlimit(10**9)
resource.setrlimit(
resource.RLIMIT_STACK, (resource.RLIM_INFINITY, resource.RLIM_INFINITY)
)


def main():
args = cli.args.parse_args()
iter = args.iterations
for _ in range(iter):
stdin = []

n = randint(1, 2 * 10**5)
q = randint(1, 2 * 10**5)
stdin.append(f"{n} {q}")

par = random.rand_tree_parent_list(n=n)[1:]
stdin.append(" ".join(map(str, par)))

for _ in range(q):
a = randint(1, n)
b = randint(1, n)
stdin.append(f"{a} {b}")

proc_ans, sol_ans = runner.run(args.program, args.solution, "\n".join(stdin))
assert proc_ans == sol_ans


if __name__ == "__main__":
main()
21 changes: 21 additions & 0 deletions test/checker/matrix.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from test.lib import cli, runner
from random import randint


def main():
args = cli.args.parse_args()
iter = args.iterations
for _ in range(iter):
stdin = []

n = randint(3, 10**9)
l = randint(1, 75)
r = randint(l, 75)
stdin.append(f"{n} {l} {r}")

proc_ans, sol_ans = runner.run(args.program, args.solution, "\n".join(stdin))
assert len(proc_ans) == len(sol_ans)


if __name__ == "__main__":
main()
30 changes: 30 additions & 0 deletions test/checker/mo_array.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from test.lib import cli, runner
from random import randint


def main():
args = cli.args.parse_args()
iter = args.iterations
for _ in range(iter):
stdin = []

n = randint(1, 2 * 10**5)
t = randint(1, 2 * 10**5)
stdin.append(f"{n} {t}")

a = [randint(1, 10**6) for _ in range(n)]
stdin.append(" ".join(map(str, a)))

for _ in range(t):
a = randint(1, n)
b = randint(a, n)
stdin.append(f"{a} {b}")

proc_ans, sol_ans = runner.run(args.program, args.solution, "\n".join(stdin))
if proc_ans != sol_ans:
print(proc_ans)
assert proc_ans == sol_ans


if __name__ == "__main__":
main()
22 changes: 22 additions & 0 deletions test/checker/rolling_hash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from test.lib import cli, runner
from random import randint, choices
from string import ascii_lowercase


def main():
args = cli.args.parse_args()
iter = args.iterations
for _ in range(iter):
stdin = []

n = randint(1, 10**5)
s = "".join(choices(ascii_lowercase, k=n))
stdin.append(s)

proc_ans, sol_ans = runner.run(args.program, args.solution, "\n".join(stdin))
assert len(proc_ans) == len(sol_ans)
assert proc_ans in s and sol_ans in s


if __name__ == "__main__":
main()
Loading