mirror of
https://github.com/ggerganov/llama.cpp.git
synced 2026-02-05 13:53:23 +02:00
Some checks failed
Build Actions Cache / ubuntu-24-vulkan-cache (push) Has been cancelled
Build Actions Cache / ubuntu-24-spacemit-cache (push) Has been cancelled
Build Actions Cache / windows-2022-rocm-cache (push) Has been cancelled
CI / macOS-latest-cmake-arm64 (push) Has been cancelled
CI / macOS-latest-cmake-x64 (push) Has been cancelled
CI / macOS-latest-cmake-arm64-webgpu (push) Has been cancelled
CI / ubuntu-cpu-cmake (arm64, ubuntu-22.04-arm) (push) Has been cancelled
CI / ubuntu-cpu-cmake (ppc64le, ubuntu-24.04-ppc64le) (push) Has been cancelled
CI / ubuntu-cpu-cmake (s390x, ubuntu-24.04-s390x) (push) Has been cancelled
CI / ubuntu-cpu-cmake (x64, ubuntu-22.04) (push) Has been cancelled
CI / ubuntu-latest-cmake-sanitizer (Debug, ADDRESS) (push) Has been cancelled
CI / ubuntu-latest-cmake-sanitizer (Debug, THREAD) (push) Has been cancelled
CI / ubuntu-latest-cmake-sanitizer (Debug, UNDEFINED) (push) Has been cancelled
CI / ubuntu-latest-llguidance (push) Has been cancelled
CI / ubuntu-latest-cmake-rpc (push) Has been cancelled
CI / ubuntu-24-cmake-vulkan-deb (push) Has been cancelled
CI / ubuntu-24-cmake-vulkan (push) Has been cancelled
CI / ubuntu-24-cmake-webgpu (push) Has been cancelled
CI / ubuntu-24-wasm-webgpu (push) Has been cancelled
CI / ubuntu-22-cmake-hip (push) Has been cancelled
CI / ubuntu-22-cmake-musa (push) Has been cancelled
CI / ubuntu-22-cmake-sycl (push) Has been cancelled
CI / ubuntu-22-cmake-sycl-fp16 (push) Has been cancelled
CI / build-linux-cross (push) Has been cancelled
CI / build-cmake-pkg (push) Has been cancelled
CI / macOS-latest-cmake-ios (push) Has been cancelled
CI / macOS-latest-cmake-tvos (push) Has been cancelled
CI / macOS-latest-cmake-visionos (push) Has been cancelled
CI / windows-msys2 (Release, clang-x86_64, CLANG64) (push) Has been cancelled
CI / windows-msys2 (Release, ucrt-x86_64, UCRT64) (push) Has been cancelled
CI / windows-latest-cmake (arm64, llvm-arm64, -G "Ninja Multi-Config" -D CMAKE_TOOLCHAIN_FILE=cmake/arm64-windows-llvm.cmake -DGGML_NATIVE=OFF -DLLAMA_BUILD_SERVER=ON) (push) Has been cancelled
CI / windows-latest-cmake (arm64, llvm-arm64-opencl-adreno, -G "Ninja Multi-Config" -D CMAKE_TOOLCHAIN_FILE=cmake/arm64-windows-llvm.cmake -DCMAKE_PREFIX_PATH="$env:RUNNER_TEMP/opencl-arm64-release" -DGGML_OPENCL=ON -DGGML_OPENCL_USE_ADRENO_KERNELS=ON) (push) Has been cancelled
CI / windows-latest-cmake (x64, cpu-x64 (static), -G "Ninja Multi-Config" -D CMAKE_TOOLCHAIN_FILE=cmake/x64-windows-llvm.cmake -DGGML_NATIVE=OFF -DLLAMA_BUILD_SERVER=ON -DGGML_RPC=ON -DBUILD_SHARED_LIBS=OFF) (push) Has been cancelled
CI / windows-latest-cmake (x64, openblas-x64, -G "Ninja Multi-Config" -D CMAKE_TOOLCHAIN_FILE=cmake/x64-windows-llvm.cmake -DGGML_NATIVE=OFF -DLLAMA_BUILD_SERVER=ON -DGGML_RPC=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON -DGGML_OPENMP=OFF -DGGML_BLAS=… (push) Has been cancelled
CI / windows-latest-cmake (x64, vulkan-x64, -DCMAKE_BUILD_TYPE=Release -DGGML_NATIVE=OFF -DLLAMA_BUILD_SERVER=ON -DGGML_RPC=ON -DGGML_BACKEND_DL=ON -DGGML_CPU_ALL_VARIANTS=ON -DGGML_VULKAN=ON) (push) Has been cancelled
CI / ubuntu-latest-cmake-cuda (push) Has been cancelled
CI / windows-2022-cmake-cuda (12.4) (push) Has been cancelled
CI / windows-latest-cmake-sycl (push) Has been cancelled
CI / windows-latest-cmake-hip (push) Has been cancelled
CI / ios-xcode-build (push) Has been cancelled
CI / android-build (push) Has been cancelled
CI / android-ndk-build (arm64-cpu, -D ANDROID_ABI=arm64-v8a -D ANDROID_PLATFORM=android-31 -D CMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -D GGML_NATIVE=OFF -DGGML_CPU_ARM_ARCH=armv8.5-a+fp16+i8mm -G Ninja -D LLAMA_CURL=OFF … (push) Has been cancelled
CI / android-ndk-build (arm64-snapdragon, --preset arm64-android-snapdragon-release) (push) Has been cancelled
CI / openEuler-latest-cmake-cann (aarch64, Release, 310p) (push) Has been cancelled
CI / openEuler-latest-cmake-cann (aarch64, Release, 910b) (push) Has been cancelled
CI / openEuler-latest-cmake-cann (x86, Release, 310p) (push) Has been cancelled
CI / openEuler-latest-cmake-cann (x86, Release, 910b) (push) Has been cancelled
CI / ggml-ci-x64-cpu-low-perf (push) Has been cancelled
CI / ggml-ci-arm64-cpu-low-perf (push) Has been cancelled
CI / ggml-ci-x64-cpu-high-perf (push) Has been cancelled
CI / ggml-ci-arm64-cpu-high-perf (push) Has been cancelled
CI / ggml-ci-arm64-cpu-high-perf-sve (push) Has been cancelled
CI / ggml-ci-x64-nvidia-cuda (push) Has been cancelled
CI / ggml-ci-x64-nvidia-vulkan-cm (push) Has been cancelled
CI / ggml-ci-x64-nvidia-vulkan-cm2 (push) Has been cancelled
CI / ggml-ci-x64-cpu-amx (push) Has been cancelled
CI / ggml-ci-mac-metal (push) Has been cancelled
CI / ggml-ci-mac-vulkan (push) Has been cancelled
CI / ggml-ci-arm64-cpu-kleidiai (push) Has been cancelled
CI / ubuntu-cpu-cmake-riscv64-native (push) Has been cancelled
CI / ubuntu-cmake-sanitizer-riscv64-native (Debug, ADDRESS) (push) Has been cancelled
CI / ubuntu-cmake-sanitizer-riscv64-native (Debug, THREAD) (push) Has been cancelled
CI / ubuntu-cmake-sanitizer-riscv64-native (Debug, UNDEFINED) (push) Has been cancelled
CI / ubuntu-llguidance-riscv64-native (push) Has been cancelled
CI / ubuntu-cmake-rpc-riscv64-native (push) Has been cancelled
CI / ggml-ci-arm64-graviton4-kleidiai (push) Has been cancelled
CI / macOS-latest-swift (generic/platform=iOS) (push) Has been cancelled
CI / macOS-latest-swift (generic/platform=macOS) (push) Has been cancelled
CI / macOS-latest-swift (generic/platform=tvOS) (push) Has been cancelled
* grammar : add support for std::regex_search() with trigger patterns * common : update hermes2 pro trigger to search instead of match * common : use regex_search with anchoring for partial matching * common : adjust regex partial tests to use new pattern * grammar : check pattern directly instead of adding a type * common : adjust existing patterns to match new semantics
289 lines
11 KiB
C++
289 lines
11 KiB
C++
// Tests common_regex (esp. its partial final matches support).
|
|
|
|
#include "common.h"
|
|
#include "regex-partial.h"
|
|
|
|
#include <sstream>
|
|
#include <iostream>
|
|
#include <optional>
|
|
|
|
template <class T> static void assert_equals(const T & expected, const T & actual) {
|
|
if (expected != actual) {
|
|
std::cerr << "Expected: " << expected << std::endl;
|
|
std::cerr << " Actual: " << actual << std::endl;
|
|
std::cerr << std::flush;
|
|
throw std::runtime_error("Test failed");
|
|
}
|
|
}
|
|
|
|
struct test_case {
|
|
std::string pattern;
|
|
struct input_output {
|
|
std::string input;
|
|
common_regex_match output;
|
|
};
|
|
std::vector<input_output> inputs_outputs;
|
|
};
|
|
|
|
static std::string common_regex_match_type_name(common_regex_match_type type) {
|
|
switch (type) {
|
|
case COMMON_REGEX_MATCH_TYPE_NONE:
|
|
return "COMMON_REGEX_MATCH_TYPE_NONE";
|
|
case COMMON_REGEX_MATCH_TYPE_PARTIAL:
|
|
return "COMMON_REGEX_MATCH_TYPE_PARTIAL";
|
|
case COMMON_REGEX_MATCH_TYPE_FULL:
|
|
return "COMMON_REGEX_MATCH_TYPE_FULL";
|
|
}
|
|
return "?";
|
|
}
|
|
|
|
static void test_regex() {
|
|
printf("[%s]\n", __func__);
|
|
auto test = [](const test_case & test_case) {
|
|
common_regex cr(test_case.pattern);
|
|
std::cout << "Testing pattern: /" << test_case.pattern << "/\n";
|
|
// std::cout << " partial rev: " << cr.reversed_partial_pattern.str() << '\n';
|
|
for (const auto & input_output : test_case.inputs_outputs) {
|
|
std::cout << " Input: " << input_output.input << '\n';
|
|
auto m = cr.search(input_output.input, 0);
|
|
if (m != input_output.output) {
|
|
auto match_to_str = [&](const std::optional<common_regex_match> & m) {
|
|
std::ostringstream ss;
|
|
if (m->type == COMMON_REGEX_MATCH_TYPE_NONE) {
|
|
ss << "<no match>";
|
|
} else {
|
|
GGML_ASSERT(!input_output.output.groups.empty());
|
|
std::vector<std::string> parts;
|
|
for (const auto & g : m->groups) {
|
|
parts.push_back("{" + std::to_string(g.begin) + ", " + std::to_string(g.end) + "}");
|
|
}
|
|
ss << "{" << common_regex_match_type_name(m->type) << ", {" << string_join(parts, ", ") << "}}";
|
|
}
|
|
return ss.str();
|
|
};
|
|
std::cout << " Expected: " << match_to_str(input_output.output) << '\n';
|
|
std::cout << " Got: " << match_to_str(m) << '\n';
|
|
std::cout << " Inverted pattern: /" << regex_to_reversed_partial_regex(test_case.pattern) << "/\n";
|
|
|
|
throw std::runtime_error("Test failed");
|
|
}
|
|
}
|
|
};
|
|
test({
|
|
"a",
|
|
{
|
|
{"a", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 1}}}},
|
|
{"b", {COMMON_REGEX_MATCH_TYPE_NONE, {}}},
|
|
{"ab", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 1}}}},
|
|
{"ba", {COMMON_REGEX_MATCH_TYPE_FULL, {{1, 2}}}},
|
|
}
|
|
});
|
|
test({
|
|
"abcd",
|
|
{
|
|
{"abcd", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 4}}}},
|
|
{"abcde", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 4}}}},
|
|
{"abc", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 3}}}},
|
|
{"ab", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 2}}}},
|
|
{"a", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 1}}}},
|
|
{"d", {}},
|
|
{"bcd", {}},
|
|
{"cde", {}},
|
|
{"cd", {}},
|
|
{"yeah ab", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{5, 7}}}},
|
|
{"abbie", {}},
|
|
{"", {}},
|
|
}
|
|
});
|
|
test({
|
|
".*?ab",
|
|
{
|
|
{"ab", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 2}}}},
|
|
{"abc", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 2}}}},
|
|
{"dab", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 3}}}},
|
|
{"dabc", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 3}}}},
|
|
{"da", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 2}}}},
|
|
{"d", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 1}}}},
|
|
}
|
|
});
|
|
test({
|
|
"a.*?b",
|
|
{
|
|
{"ab", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 2}}}},
|
|
{"abc", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 2}}}},
|
|
{"a b", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 3}}}},
|
|
{"a", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 1}}}},
|
|
{"argh", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 4}}}},
|
|
{"d", {}},
|
|
{"b", {}},
|
|
}
|
|
});
|
|
test({
|
|
"ab(?:cd){2,4}ef",
|
|
{
|
|
// {"ab", {COMMON_REGEX_MATCH_TYPE_PARTIAL, 0, {}}},
|
|
{"ab", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 2}}}},
|
|
{"abcd", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 4}}}},
|
|
{"abcde", {}},
|
|
{"abcdef", {}},
|
|
{"abcdcd", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 6}}}},
|
|
{"abcdcde", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 7}}}},
|
|
{"abcdcdef", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 8}}}},
|
|
{"abcdcdcdcdef", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 12}}}},
|
|
{"abcdcdcdcdcdef", {}},
|
|
{"abcde", {}},
|
|
{"yea", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{2, 3}}}},
|
|
}
|
|
});
|
|
test({
|
|
"a(?:rte| pure )fact",
|
|
{
|
|
{"a", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 1}}}},
|
|
{"art", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 3}}}},
|
|
{"artefa", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 6}}}},
|
|
{"fact", {}},
|
|
{"an arte", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{3, 7}}}},
|
|
{"artefact", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 8}}}},
|
|
{"an artefact", {COMMON_REGEX_MATCH_TYPE_FULL, {{3, 11}}}},
|
|
{"a pure", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 6}}}},
|
|
{"a pure fact", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 11}}}},
|
|
{"it's a pure fact", {COMMON_REGEX_MATCH_TYPE_FULL, {{5, 16}}}},
|
|
{"" , {}},
|
|
{"pure", {}},
|
|
{"pure fact", {}},
|
|
}
|
|
});
|
|
test({
|
|
"abc",
|
|
{
|
|
{" abcc", {COMMON_REGEX_MATCH_TYPE_FULL, {{1, 4}}}},
|
|
{"ab", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 2}}}},
|
|
{"abc", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 3}}}},
|
|
{" ab", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{1, 3}}}},
|
|
{"a", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 1}}}},
|
|
{"b", {}},
|
|
{"c", {}},
|
|
{"", {}},
|
|
}
|
|
});
|
|
|
|
test({
|
|
"(?:abc)?\\s*def",
|
|
{
|
|
{"ab", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 2}}}},
|
|
{"abc", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 3}}}},
|
|
{"abc ", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 4}}}},
|
|
{"abc d", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 5}}}},
|
|
{"abc de", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 6}}}},
|
|
{"abc def", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 7}}}},
|
|
{"abc defg", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 7}}}},
|
|
{"abc defgh", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 7}}}},
|
|
{"abcde", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 5}}}},
|
|
{"abcdefgh", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 6}}}},
|
|
{" d", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 2}}}},
|
|
{"def", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 3}}}},
|
|
}
|
|
});
|
|
|
|
test({
|
|
"a+b",
|
|
{
|
|
{"aaab", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 4}}}},
|
|
{"aaa", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 3}}}},
|
|
{"ab", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 2}}}},
|
|
}
|
|
});
|
|
|
|
test({
|
|
"(?:"
|
|
"(```(?:xml|json)?\\n\\s*)?" // match 1 (block_start)
|
|
"(" // match 2 (open_tag)
|
|
"<tool_call>"
|
|
"|<function_call>"
|
|
"|<tool>"
|
|
"|<tools>"
|
|
"|<response>"
|
|
"|<json>"
|
|
"|<xml>"
|
|
"|<JSON>"
|
|
")?"
|
|
"(\\s*\\{\\s*\"name\"\\s*:)" // match 3 (named tool call)
|
|
")"
|
|
"|<function=([^>]+)>" // match 4 (function name)
|
|
"|<function name=\"([^\"]+)\">", // match 5 (function name again)
|
|
{
|
|
{"{\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 8}, {54, 54}, {54, 54}, {0, 8}, {54, 54}, {54, 54}}}},
|
|
{"<tool_call> {\"name", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 18}}}},
|
|
{"<tool_call>{\"name", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 17}}}},
|
|
{"Let's call something\n<tool_call>{\"name", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{21, 38}}}},
|
|
{"Ok then<tool_call>{\"name", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{7, 24}}}},
|
|
{"{\"name", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{0, 6}}}},
|
|
{"Ok then{\"name", {COMMON_REGEX_MATCH_TYPE_PARTIAL, {{7, 13}}}},
|
|
{"<tool_call> {\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 20}, {66, 66}, {0, 11}, {11, 20}, {66, 66}, {66, 66}}}},
|
|
{"<function_call> {\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 24}, {70, 70}, {0, 15}, {15, 24}, {70, 70}, {70, 70}}}},
|
|
{"<function name=\"special_function\"> {\"name\": \"special_function\", \"arguments\": {\"arg1\": 1}}", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 34}, {89, 89}, {89, 89}, {89, 89}, {89, 89}, {16, 32}}}},
|
|
{"<function=all>", {COMMON_REGEX_MATCH_TYPE_FULL, {{0, 14}, {14, 14}, {14, 14}, {14, 14}, {10, 13}, {14, 14}}}},
|
|
|
|
}
|
|
});
|
|
}
|
|
|
|
static void test_regex_to_reversed_partial_regex() {
|
|
printf("[%s]\n", __func__);
|
|
|
|
assert_equals<std::string>(
|
|
"^((?:(?:c)?b)?a)",
|
|
regex_to_reversed_partial_regex("abc"));
|
|
|
|
assert_equals<std::string>(
|
|
"^(a+)",
|
|
regex_to_reversed_partial_regex("a+"));
|
|
|
|
assert_equals<std::string>(
|
|
"^(a*)",
|
|
regex_to_reversed_partial_regex("a*"));
|
|
|
|
assert_equals<std::string>(
|
|
"^(a?)",
|
|
regex_to_reversed_partial_regex("a?"));
|
|
|
|
assert_equals<std::string>(
|
|
"^([a-z])",
|
|
regex_to_reversed_partial_regex("[a-z]"));
|
|
|
|
assert_equals<std::string>(
|
|
"^((?:\\w+)?[a-z])",
|
|
regex_to_reversed_partial_regex("[a-z]\\w+"));
|
|
|
|
assert_equals<std::string>(
|
|
"^((?:a|b))",
|
|
regex_to_reversed_partial_regex("(?:a|b)"));
|
|
assert_equals<std::string>(
|
|
"^((?:(?:(?:d)?c)?b)?a)",
|
|
regex_to_reversed_partial_regex("abcd"));
|
|
assert_equals<std::string>(
|
|
"^((?:b)?a*)", // TODO: ((?:b)?a*+).* ??
|
|
regex_to_reversed_partial_regex("a*b"));
|
|
assert_equals<std::string>(
|
|
"^((?:(?:b)?a)?.*)",
|
|
regex_to_reversed_partial_regex(".*?ab"));
|
|
assert_equals<std::string>(
|
|
"^((?:(?:b)?.*)?a)",
|
|
regex_to_reversed_partial_regex("a.*?b"));
|
|
assert_equals<std::string>(
|
|
"^((?:(?:d)?(?:(?:c)?b))?a)",
|
|
regex_to_reversed_partial_regex("a(bc)d"));
|
|
assert_equals<std::string>(
|
|
"^((?:(?:(?:c)?b|(?:e)?d))?a)",
|
|
regex_to_reversed_partial_regex("a(bc|de)"));
|
|
assert_equals<std::string>(
|
|
"^((?:(?:(?:(?:(?:c)?b?)?b?)?b)?b)?a)",
|
|
regex_to_reversed_partial_regex("ab{2,4}c"));
|
|
}
|
|
|
|
int main() {
|
|
test_regex_to_reversed_partial_regex();
|
|
test_regex();
|
|
std::cout << "All tests passed.\n";
|
|
}
|