Skip to content

Commit 1688a7b

Browse files
author
Yurii A.
committed
WIP: add suggested tests skeleton
* refactor extract_snippets.py to take arguments from command line * add logging to extract_snippets.py script * add suggested CMakeLists.txt * add google test library, add example refactoring for 2sat and aho korasick tests
1 parent 6eecd4a commit 1688a7b

File tree

6 files changed

+286
-41
lines changed

6 files changed

+286
-41
lines changed

test/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
/*.h
1+
include/snippets/
2+
build/

test/CMakeLists.txt

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
cmake_minimum_required(VERSION 3.30 FATAL_ERROR)
2+
3+
project(cp-algorithms LANGUAGES CXX)
4+
5+
# generating snippets
6+
set(SNIPPETS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include/snippets)
7+
find_package(Python3 3.10 REQUIRED COMPONENTS Interpreter)
8+
execute_process(
9+
COMMAND
10+
${Python3_EXECUTABLE}
11+
"${CMAKE_CURRENT_SOURCE_DIR}/scripts/extract_snippets.py"
12+
--src-dir=${CMAKE_CURRENT_SOURCE_DIR}/../src
13+
--target-dir=${SNIPPETS_DIR}
14+
--remove-prev-target-dir
15+
COMMAND_ERROR_IS_FATAL ANY
16+
)
17+
18+
# loading googletest
19+
include(FetchContent)
20+
FetchContent_Declare(
21+
googletest
22+
GIT_REPOSITORY https://github.com/google/googletest.git
23+
GIT_TAG v1.17.0
24+
)
25+
# For Windows: Prevent overriding the parent project's compiler/linker settings
26+
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
27+
FetchContent_MakeAvailable(googletest)
28+
include(GoogleTest)
29+
30+
set(CMAKE_CXX_STANDARD 20)
31+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
32+
set(CMAKE_CXX_EXTENSIONS OFF)
33+
34+
file(GLOB_RECURSE SNIPPETS_SOURCES CONFIGURE_DEPENDS ${SNIPPETS_DIR}/*.h)
35+
file(GLOB_RECURSE TEST_SOURCES CONFIGURE_DEPENDS src/test_*.cpp)
36+
37+
add_executable(main ${TEST_SOURCES} ${SNIPPETS_SOURCES})
38+
target_link_libraries(main PRIVATE GTest::gtest_main GTest::gtest GTest::gmock)
39+
target_include_directories(main PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
40+
41+
enable_testing()
42+
gtest_discover_tests(main)

test/extract_snippets.py

Lines changed: 0 additions & 40 deletions
This file was deleted.

test/scripts/extract_snippets.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import argparse
2+
import re
3+
import os
4+
import sys
5+
import shutil
6+
import logging
7+
from typing import List, Optional
8+
from dataclasses import dataclass
9+
10+
11+
@dataclass
12+
class Snippet:
13+
name: str
14+
lines: List[str]
15+
16+
17+
def write_snippet(target_dir: os.PathLike, snippet: Snippet):
18+
assert os.path.exists(target_dir) and os.path.isdir(target_dir)
19+
20+
file_name = f'{snippet.name}.h'
21+
with open(os.path.join(target_dir, file_name), 'w', encoding='utf-8') as f:
22+
f.writelines(snippet.lines)
23+
24+
25+
def extract_snippets(filepath: os.PathLike) -> List[Snippet]:
26+
with open(filepath, 'r', encoding='utf-8') as f:
27+
lines = f.readlines()
28+
29+
snippets = []
30+
31+
snippet_start = re.compile(r"^```\{.cpp\s+file=(\S+)\}$")
32+
snippet_end = re.compile(r"^```$")
33+
34+
snippet_start_line: Optional[int] = None
35+
snippet_name: Optional[str] = None
36+
37+
for line_idx, line in enumerate(lines):
38+
match_snippet_start = snippet_start.match(line)
39+
match_snippet_end = snippet_end.match(line)
40+
assert not (match_snippet_start and match_snippet_end)
41+
42+
if match_snippet_start:
43+
assert snippet_start_line is None
44+
assert snippet_name is None
45+
46+
snippet_start_line = line_idx
47+
snippet_name = match_snippet_start.group(1)
48+
elif match_snippet_end:
49+
if snippet_start_line is not None:
50+
assert snippet_start_line is not None
51+
assert snippet_name is not None
52+
53+
snippet = lines[snippet_start_line + 1: line_idx]
54+
55+
snippets.append(Snippet(name=snippet_name, lines=snippet))
56+
57+
snippet_start_line = None
58+
snippet_name = None
59+
60+
return snippets
61+
62+
63+
def main(args: argparse.Namespace) -> None:
64+
src_dir = args.src_dir
65+
target_dir = args.target_dir
66+
67+
logging.info(f'--src-dir="{src_dir}"')
68+
logging.info(f'--target-dir="{target_dir}"')
69+
70+
assert os.path.isdir(src_dir)
71+
72+
if args.remove_prev_target_dir and os.path.exists(target_dir):
73+
logging.info(f'Script launched with --remove-prev-target-dir flag')
74+
logging.info(f'Removing --target-dir="{target_dir}"')
75+
shutil.rmtree(target_dir)
76+
77+
if not os.path.exists(target_dir):
78+
logging.info(
79+
f'--target-dir="{target_dir}" does not exist, creating')
80+
os.makedirs(target_dir, exist_ok=False)
81+
assert os.path.isdir(
82+
target_dir), f'Failed to create --target-dir: "{target_dir}"'
83+
84+
snippets = []
85+
86+
for subdir, _, files in os.walk(src_dir):
87+
for filename in files:
88+
if filename.lower().endswith('.md'):
89+
filepath = os.path.join(subdir, filename)
90+
logging.debug(f'Extracting snippets from {filename}')
91+
snippets.extend(extract_snippets(filepath))
92+
93+
n_snippets = len(snippets)
94+
for snippet_idx, snippet in enumerate(snippets, start=1):
95+
logging.debug(
96+
f'({snippet_idx}/{n_snippets}) writing snippet {snippet.name} to "{target_dir}"')
97+
write_snippet(target_dir, snippet)
98+
99+
logging.info(
100+
f'All done, {n_snippets} snippets have been written to "{target_dir}"')
101+
102+
103+
if __name__ == '__main__':
104+
parser = argparse.ArgumentParser(
105+
description='Recursively extracts specially annotation cpp code snippets from src dir with .md files')
106+
107+
parser.add_argument('--src-dir', type=str, required=True,
108+
help='path to the directory with .md source to recursively look for cpp snippets with {.cpp file=...} annotation')
109+
parser.add_argument('--target-dir', type=str, required=True,
110+
help='path to the resulting directory with .h snippets extracted from src-dir')
111+
parser.add_argument('--remove-prev-target-dir', action='store_true',
112+
help='remove --target-dir prior to generating snippets')
113+
114+
logging_level_names = list(logging.getLevelNamesMapping().keys())
115+
assert 'INFO' in logging_level_names
116+
parser.add_argument('--logging-level', type=str, choices=logging_level_names,
117+
default='INFO', help='script logging level')
118+
119+
args = parser.parse_args()
120+
121+
logging.basicConfig(
122+
stream=sys.stdout,
123+
format='%(asctime)s %(module)-15s - [%(levelname)-6s] - %(message)s',
124+
datefmt='%H:%M:%S',
125+
level=args.logging_level
126+
)
127+
128+
main(args)

test/src/test_2sat_new.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#include <gtest/gtest.h>
2+
#include <gmock/gmock.h>
3+
4+
#include <vector>
5+
using namespace std;
6+
7+
#include "snippets/2sat.h"
8+
9+
namespace
10+
{
11+
TEST(TwoSAT, ExampleUsage)
12+
{
13+
TwoSatSolver::example_usage();
14+
}
15+
16+
TEST(TwoSAT, ArticleExample)
17+
{
18+
TwoSatSolver solver(3); // a, b, c
19+
solver.add_disjunction(0, false, 1, true); // a v not b
20+
solver.add_disjunction(0, true, 1, false); // not a v b
21+
solver.add_disjunction(0, true, 1, true); // not a v not b
22+
solver.add_disjunction(0, false, 2, true); // a v not c
23+
EXPECT_TRUE(solver.solve_2SAT());
24+
auto expected = vector<bool>{{false, false, false}};
25+
EXPECT_EQ(solver.assignment, expected);
26+
}
27+
28+
TEST(TwoSAT, Unsatisfiable)
29+
{
30+
TwoSatSolver solver(2); // a, b
31+
solver.add_disjunction(0, false, 1, false); // a v b
32+
solver.add_disjunction(0, false, 1, true); // a v not b
33+
solver.add_disjunction(0, true, 1, false); // not a v b
34+
solver.add_disjunction(0, true, 1, true); // not a v not b
35+
EXPECT_FALSE(solver.solve_2SAT());
36+
}
37+
38+
TEST(TwoSAT, OtherSatisfiableExample)
39+
{
40+
TwoSatSolver solver(4); // a, b, c, d
41+
solver.add_disjunction(0, false, 1, true); // a v not b
42+
solver.add_disjunction(0, true, 2, true); // not a v not c
43+
solver.add_disjunction(0, false, 1, false); // a v b
44+
solver.add_disjunction(3, false, 2, true); // d v not c
45+
solver.add_disjunction(3, false, 0, true); // d v not a
46+
EXPECT_TRUE(solver.solve_2SAT());
47+
// two solutions
48+
auto expected_1 = vector<bool>{{true, true, false, true}};
49+
auto expected_2 = vector<bool>{{true, false, false, true}};
50+
EXPECT_THAT(solver.assignment, ::testing::AnyOf(expected_1, expected_2));
51+
}
52+
53+
} // namespace

test/src/test_aho_korasick_new.cpp

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#include <gtest/gtest.h>
2+
3+
#include <vector>
4+
#include <string>
5+
#include <iostream>
6+
using namespace std;
7+
8+
namespace {
9+
10+
namespace Trie {
11+
#include "snippets/aho_corasick_trie_definition.h"
12+
#include "snippets/aho_corasick_trie_add.h"
13+
} // namespace Trie
14+
15+
namespace Automaton {
16+
#include "snippets/aho_corasick_automaton.h"
17+
} // namespace Automation
18+
19+
TEST(AhoKorasick, TrieAddString)
20+
{
21+
using namespace Trie;
22+
23+
vector<string> set = {"a", "to", "tea", "ted", "ten", "i", "in", "inn"};
24+
for (string s : set) {
25+
add_string(s);
26+
}
27+
28+
EXPECT_EQ(trie.size(), 11);
29+
}
30+
31+
TEST(AhoKorasick, TrieAutomation)
32+
{
33+
using namespace Automaton;
34+
35+
vector<string> set = {"a", "ab", "bab", "bc", "bca", "c", "caa"};
36+
for (string s : set) {
37+
add_string(s);
38+
}
39+
EXPECT_EQ(t.size(), 11);
40+
41+
int v = 0;
42+
v = go(v, 'a');
43+
EXPECT_TRUE(t[v].output);
44+
v = go(v, 'b');
45+
EXPECT_TRUE(t[v].output);
46+
v = go(v, 'c');
47+
EXPECT_TRUE(t[v].output);
48+
v = go(v, 'd');
49+
EXPECT_FALSE(t[v].output);
50+
EXPECT_EQ(v, 0);
51+
v = go(v, 'b');
52+
EXPECT_FALSE(t[v].output);
53+
v = go(v, 'a');
54+
EXPECT_FALSE(t[v].output);
55+
v = go(v, 'a');
56+
EXPECT_TRUE(t[v].output);
57+
v = go(v, 'b');
58+
EXPECT_TRUE(t[v].output);
59+
}
60+
61+
} // namespace

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy