Skip to content

Commit a433081

Browse files
committed
test: establish compatibility and security baselines
1 parent 3c0a3a0 commit a433081

24 files changed

Lines changed: 1064 additions & 1 deletion

.github/workflows/build-dockerhub.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,10 @@ jobs:
297297
python3 scripts/verify_custom_openclash_rules_bundle.py \
298298
--root base/Custom_OpenClash_Rules
299299
300+
- name: Run Python regression baselines
301+
shell: bash
302+
run: python3 -m unittest discover -s tests -p 'test_*.py' -v
303+
300304
- name: Set up Docker Buildx
301305
shell: bash
302306
run: |

.github/workflows/codeql.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,8 +250,9 @@ jobs:
250250
- name: Build (manual)
251251
if: matrix.language == 'c-cpp'
252252
run: |
253-
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/usr/local
253+
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/usr/local -DBUILD_TESTS=ON
254254
cmake --build build -j$(nproc)
255+
python3 scripts/run-test-suite.py --build-dir build --mode full
255256
256257
- name: Perform CodeQL Analysis
257258
uses: github/codeql-action/analyze@v4

CMakeLists.txt

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ ADD_EXECUTABLE(${BUILD_TARGET_NAME}
8989
src/handler/webget.cpp
9090
src/handler/proxy_policy.cpp
9191
src/handler/settings.cpp
92+
src/handler/settings_snapshot.cpp
9293
src/handler/sub_request_key.cpp
9394
src/main.cpp
9495
src/parser/infoparser.cpp
@@ -227,6 +228,17 @@ IF(BUILD_TESTS)
227228
NAME custom_openclash_rules_bundle
228229
COMMAND ${Python3_EXECUTABLE}
229230
${CMAKE_SOURCE_DIR}/tests/test_custom_openclash_rules_bundle.py)
231+
SET_TESTS_PROPERTIES(custom_openclash_rules_bundle PROPERTIES LABELS fast)
232+
233+
ADD_TEST(
234+
NAME compatibility_security_baseline
235+
COMMAND ${Python3_EXECUTABLE}
236+
${CMAKE_SOURCE_DIR}/tests/compatibility_security_baseline.py
237+
--binary $<TARGET_FILE:${BUILD_TARGET_NAME}>)
238+
SET_TESTS_PROPERTIES(compatibility_security_baseline PROPERTIES
239+
LABELS full
240+
TIMEOUT 120
241+
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
230242

231243
ADD_EXECUTABLE(proxy_policy_test
232244
tests/proxy_policy_test.cpp
@@ -237,6 +249,7 @@ IF(BUILD_TESTS)
237249
TARGET_INCLUDE_DIRECTORIES(proxy_policy_test PRIVATE src)
238250
TARGET_LINK_LIBRARIES(proxy_policy_test ${CMAKE_THREAD_LIBS_INIT})
239251
ADD_TEST(NAME proxy_policy COMMAND proxy_policy_test)
252+
SET_TESTS_PROPERTIES(proxy_policy PROPERTIES LABELS fast)
240253

241254
ADD_EXECUTABLE(custom_openclash_rules_test
242255
tests/custom_openclash_rules_test.cpp
@@ -254,13 +267,15 @@ IF(BUILD_TESTS)
254267
${YAML_CPP_LIBRARY})
255268
ADD_TEST(NAME custom_openclash_rules COMMAND custom_openclash_rules_test)
256269
SET_TESTS_PROPERTIES(custom_openclash_rules PROPERTIES
270+
LABELS fast
257271
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
258272

259273
ADD_EXECUTABLE(ruleset_options_test
260274
tests/ruleset_options_test.cpp
261275
src/config/ruleset.cpp)
262276
TARGET_INCLUDE_DIRECTORIES(ruleset_options_test PRIVATE src)
263277
ADD_TEST(NAME ruleset_options COMMAND ruleset_options_test)
278+
SET_TESTS_PROPERTIES(ruleset_options PROPERTIES LABELS fast)
264279

265280
ADD_EXECUTABLE(external_rules_test
266281
tests/external_rules_test.cpp
@@ -273,20 +288,23 @@ IF(BUILD_TESTS)
273288
TARGET_LINK_LIBRARIES(external_rules_test
274289
${YAML_CPP_LIBRARY})
275290
ADD_TEST(NAME external_rules COMMAND external_rules_test)
291+
SET_TESTS_PROPERTIES(external_rules PROPERTIES LABELS fast)
276292

277293
ADD_EXECUTABLE(concurrency_primitives_test
278294
tests/concurrency_primitives_test.cpp)
279295
TARGET_INCLUDE_DIRECTORIES(concurrency_primitives_test PRIVATE src)
280296
TARGET_LINK_LIBRARIES(concurrency_primitives_test
281297
${CMAKE_THREAD_LIBS_INIT})
282298
ADD_TEST(NAME concurrency_primitives COMMAND concurrency_primitives_test)
299+
SET_TESTS_PROPERTIES(concurrency_primitives PROPERTIES LABELS fast)
283300

284301
ADD_EXECUTABLE(sub_request_key_test
285302
tests/sub_request_key_test.cpp
286303
src/handler/sub_request_key.cpp
287304
src/utils/md5/md5.cpp)
288305
TARGET_INCLUDE_DIRECTORIES(sub_request_key_test PRIVATE src)
289306
ADD_TEST(NAME sub_request_key COMMAND sub_request_key_test)
307+
SET_TESTS_PROPERTIES(sub_request_key PROPERTIES LABELS fast)
290308

291309
ADD_EXECUTABLE(curl_handle_pool_test
292310
tests/curl_handle_pool_test.cpp
@@ -302,6 +320,17 @@ IF(BUILD_TESTS)
302320
TARGET_LINK_LIBRARIES(curl_handle_pool_test ws2_32)
303321
ENDIF()
304322
ADD_TEST(NAME curl_handle_pool COMMAND curl_handle_pool_test)
323+
SET_TESTS_PROPERTIES(curl_handle_pool PROPERTIES LABELS fast)
324+
325+
ADD_EXECUTABLE(file_scope_test
326+
tests/file_scope_test.cpp
327+
src/utils/file.cpp
328+
src/utils/string.cpp)
329+
TARGET_INCLUDE_DIRECTORIES(file_scope_test PRIVATE src)
330+
ADD_TEST(NAME file_scope COMMAND file_scope_test)
331+
SET_TESTS_PROPERTIES(file_scope PROPERTIES
332+
LABELS fast
333+
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
305334
ENDIF()
306335

307336
ELSE() #BUILD_STATIC_LIBRARY

scripts/ci/docker-build-args.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ MIHOMO_CACHE_BUST="${MIHOMO_CACHE_BUST:-${DEPENDENCY_SNAPSHOT_SHA}}"
2525
SOURCE_DEPS_CACHE_BUST="${SOURCE_DEPS_CACHE_BUST:-${DEPENDENCY_SNAPSHOT_SHA}}"
2626
REFRESH_GO_DEPS="${REFRESH_GO_DEPS:-false}"
2727
REFRESH_HEADERS="${REFRESH_HEADERS:-false}"
28+
BUILD_TESTS="${BUILD_TESTS:-true}"
2829

2930
cat <<EOF
3031
THREADS=${THREADS}
@@ -50,4 +51,5 @@ DEBIAN_TRIXIE_IMAGE=${DEBIAN_TRIXIE_IMAGE}
5051
DEBIAN_TRIXIE_SLIM_IMAGE=${DEBIAN_TRIXIE_SLIM_IMAGE}
5152
REFRESH_GO_DEPS=${REFRESH_GO_DEPS}
5253
REFRESH_HEADERS=${REFRESH_HEADERS}
54+
BUILD_TESTS=${BUILD_TESTS}
5355
EOF

scripts/run-test-suite.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
#!/usr/bin/env python3
2+
"""Run the repository's fast or full CTest baseline."""
3+
4+
from __future__ import annotations
5+
6+
import argparse
7+
import subprocess
8+
import sys
9+
from pathlib import Path
10+
11+
12+
def main() -> int:
13+
parser = argparse.ArgumentParser()
14+
parser.add_argument("--build-dir", type=Path, required=True)
15+
parser.add_argument("--mode", choices=("fast", "full"), default="fast")
16+
parser.add_argument("--timeout", type=int, default=120)
17+
args = parser.parse_args()
18+
19+
command = [
20+
"ctest",
21+
"--test-dir",
22+
str(args.build_dir),
23+
"--output-on-failure",
24+
"--timeout",
25+
str(args.timeout),
26+
]
27+
if args.mode == "fast":
28+
command.extend(["--label-regex", "^fast$"])
29+
completed = subprocess.run(command, check=False)
30+
if completed.returncode:
31+
return completed.returncode
32+
print(f"{args.mode} test suite passed")
33+
return 0
34+
35+
36+
if __name__ == "__main__":
37+
raise SystemExit(main())

src/handler/settings_snapshot.cpp

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
#include "handler/settings_snapshot.h"
2+
3+
#include <string>
4+
5+
#include <nlohmann/json.hpp>
6+
7+
#include "handler/proxy_policy.h"
8+
#include "handler/settings.h"
9+
#include "utils/string.h"
10+
11+
namespace {
12+
13+
nlohmann::json sensitiveState(const std::string &value) {
14+
const std::string trimmed = trimWhitespace(value, true, true);
15+
return {
16+
{"configured", !trimmed.empty()},
17+
{"length", trimmed.size()},
18+
};
19+
}
20+
21+
std::string triState(const tribool &value) {
22+
if (value.is_undef())
23+
return "inherit";
24+
return value.get() ? "true" : "false";
25+
}
26+
27+
} // namespace
28+
29+
std::string sanitizedSettingsSnapshot(const Settings &settings) {
30+
nlohmann::json snapshot = {
31+
{"schema", 1},
32+
{"common",
33+
{
34+
{"base_path", settings.basePath},
35+
{"default_urls", sensitiveState(settings.defaultUrls)},
36+
{"insert_urls", sensitiveState(settings.insertUrls)},
37+
{"default_external_config",
38+
sensitiveState(settings.defaultExtConfig)},
39+
{"enable_insert", settings.enableInsert.get(false)},
40+
{"prepend_insert", settings.prependInsert},
41+
{"append_proxy_type", settings.appendType},
42+
{"reload_conf_on_request", settings.reloadConfOnRequest},
43+
{"exclude_remarks_count", settings.excludeRemarks.size()},
44+
{"include_remarks_count", settings.includeRemarks.size()},
45+
}},
46+
{"node_pref",
47+
{
48+
{"udp", triState(settings.UDPFlag)},
49+
{"tcp_fast_open", triState(settings.TFOFlag)},
50+
{"skip_cert_verify", triState(settings.skipCertVerify)},
51+
{"tls13", triState(settings.TLS13Flag)},
52+
{"sort", settings.enableSort},
53+
{"filter_deprecated", settings.filterDeprecated},
54+
{"append_userinfo", settings.appendUserinfo},
55+
{"clash_new_fields", settings.clashUseNewField},
56+
{"clash_proxies_style", settings.clashProxiesStyle},
57+
{"singbox_add_clash_modes", settings.singBoxAddClashModes},
58+
}},
59+
{"rules",
60+
{
61+
{"enabled", settings.enableRuleGen},
62+
{"overwrite_original", settings.overwriteOriginalRules},
63+
{"update_on_request", settings.updateRulesetOnRequest},
64+
{"ruleset_count", settings.customRulesets.size()},
65+
{"proxy_group_count", settings.customProxyGroups.size()},
66+
}},
67+
{"server",
68+
{
69+
{"listen", settings.listenAddress},
70+
{"port", settings.listenPort},
71+
{"serve_file_root", settings.serveFileRoot},
72+
{"max_pending_connections", settings.maxPendingConns},
73+
{"max_concurrent_threads", settings.maxConcurThreads},
74+
{"max_server_threads", settings.maxServerThreads},
75+
}},
76+
{"advanced",
77+
{
78+
{"max_allowed_rulesets", settings.maxAllowedRulesets},
79+
{"max_allowed_rules", settings.maxAllowedRules},
80+
{"max_allowed_download_size", settings.maxAllowedDownloadSize},
81+
{"cache_subscription", settings.cacheSubscription},
82+
{"cache_config", settings.cacheConfig},
83+
{"cache_ruleset", settings.cacheRuleset},
84+
{"serve_cache_on_fetch_fail", settings.serveCacheOnFetchFail},
85+
{"skip_failed_links", settings.skipFailedLinks},
86+
{"request_coalescing", settings.enableRequestCoalescing},
87+
{"coalesce_retry_on_5xx", settings.coalesceRetryOn5xx},
88+
{"allow_insecure_tls", settings.allowInsecureTls},
89+
{"response_cache_ttl", settings.responseCacheTtl},
90+
}},
91+
{"security",
92+
{
93+
{"profile", settings.securityProfile},
94+
{"allow_public_upload", settings.allowPublicUpload},
95+
}},
96+
{"custom_openclash_rules",
97+
{
98+
{"fallback_enabled", settings.customOpenClashRulesFallback},
99+
{"publish_enabled", settings.customOpenClashRulesPublish},
100+
}},
101+
{"proxies",
102+
{
103+
{"config", parseProxy(settings.proxyConfig).describe()},
104+
{"ruleset", parseProxy(settings.proxyRuleset).describe()},
105+
{"subscription",
106+
parseProxy(settings.proxySubscription).describe()},
107+
}},
108+
{"statistics",
109+
{
110+
{"enabled", settings.statisticsEnabled},
111+
{"data_dir", settings.statisticsDataDir},
112+
{"flush_interval", settings.statisticsFlushInterval},
113+
{"geo_provider", settings.statisticsGeoProvider},
114+
{"country_header_count",
115+
settings.statisticsCountryHeaders.size()},
116+
{"china_region_header_count",
117+
settings.statisticsChinaRegionHeaders.size()},
118+
{"dashboard_auth_enabled", settings.dashboardAuthEnabled},
119+
{"dashboard_username_configured",
120+
!settings.dashboardAuthUsername.empty()},
121+
{"dashboard_password_configured",
122+
!settings.dashboardAuthPassword.empty()},
123+
{"dashboard_max_failures", settings.dashboardAuthMaxFailures},
124+
{"dashboard_window_seconds",
125+
settings.dashboardAuthWindowSeconds},
126+
{"dashboard_lock_seconds", settings.dashboardAuthLockSeconds},
127+
}},
128+
};
129+
return snapshot.dump(2) + "\n";
130+
}

src/handler/settings_snapshot.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#ifndef SETTINGS_SNAPSHOT_H_INCLUDED
2+
#define SETTINGS_SNAPSHOT_H_INCLUDED
3+
4+
#include <string>
5+
6+
struct Settings;
7+
8+
std::string sanitizedSettingsSnapshot(const Settings &settings);
9+
10+
#endif // SETTINGS_SNAPSHOT_H_INCLUDED

src/main.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "handler/interfaces.h"
1616
#include "handler/multithread.h"
1717
#include "handler/settings.h"
18+
#include "handler/settings_snapshot.h"
1819
#include "handler/statistics.h"
1920
#include "handler/version_page.h"
2021
#include "handler/webget.h"
@@ -34,6 +35,7 @@
3435

3536
WebServer webServer;
3637
static volatile std::sig_atomic_t pendingShutdownSignal = 0;
38+
static bool settingsSnapshotMode = false;
3739

3840
#ifndef _WIN32
3941
void SetConsoleTitle(const std::string &title) {
@@ -84,6 +86,8 @@ void chkArg(int argc, char *argv[]) {
8486
if (i < argc - 1)
8587
if (freopen(argv[++i], "a", stderr) == nullptr)
8688
std::cerr << "无法将输出重定向到日志文件。\n";
89+
} else if (strcmp(argv[i], "--settings-snapshot") == 0) {
90+
settingsSnapshotMode = true;
8791
}
8892
}
8993
}
@@ -160,6 +164,10 @@ int main(int argc, char *argv[]) {
160164
SetConsoleTitle("SubConverter-Extended " VERSION);
161165
if (!readConf())
162166
return 1;
167+
if (settingsSnapshotMode) {
168+
std::cout << sanitizedSettingsSnapshot(global);
169+
return 0;
170+
}
163171
writeLog(
164172
0,
165173
"并发运行参数:HTTP base/max threads=" +

src/utils/file.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
std::string fileGet(const std::string &path, bool scope_limit = false);
2020
bool fileExist(const std::string &path, bool scope_limit = false);
21+
bool isInScope(const std::string &path);
2122
bool fileCopy(const std::string &source, const std::string &dest);
2223
int fileWrite(const std::string &path, const std::string &content, bool overwrite);
2324

0 commit comments

Comments
 (0)