feat(zapret2): add high-performance strategy generator and tuner
This commit is contained in:
@@ -68,6 +68,9 @@ export namespace Tachyon {
|
||||
GET_ZAPRET_STATUS = 'get_zapret_status',
|
||||
GET_ZAPRET2_STATUS = 'get_zapret2_status',
|
||||
GET_BYEDPI_STATUS = 'get_byedpi_status',
|
||||
ZAPRET2_TUNE = 'zapret2_tune',
|
||||
ZAPRET2_TUNE_ASYNC = 'zapret2_tune_async',
|
||||
ZAPRET2_TUNE_STATUS = 'zapret2_tune_status',
|
||||
CLASH_API = 'clash_api',
|
||||
ENABLE = 'enable',
|
||||
DISABLE = 'disable',
|
||||
|
||||
@@ -215,6 +215,9 @@ function command_spec(command) {
|
||||
validate_nfqws_strategy_json: [ "diagnostics/runtime.uc", "validate-nfqws-strategy-json", 1 ],
|
||||
validate_nfqws2_strategy_json: [ "diagnostics/runtime.uc", "validate-nfqws2-strategy-json", 1 ],
|
||||
validate_byedpi_strategy_json: [ "providers/byedpi/validator.uc", "validate-json", 1 ],
|
||||
zapret2_tune: [ "providers/zapret2/tune.uc", "tune", 3 ],
|
||||
zapret2_tune_async: [ "service/ui.uc", "zapret2-tune-async", 3 ],
|
||||
zapret2_tune_status: [ "service/ui.uc", "zapret2-tune-status", 1 ],
|
||||
generate_warp: [ "service/warp_generator.uc", "", 1 ],
|
||||
package_prerm: [ "service/package.uc", "prerm", 1 ],
|
||||
package_postinst: [ "service/package.uc", "postinst", 0 ],
|
||||
|
||||
@@ -0,0 +1,416 @@
|
||||
#!/usr/bin/env ucode
|
||||
|
||||
let fs = require("fs");
|
||||
let constants = require("core.constants");
|
||||
let uci_core = require("core.uci");
|
||||
let zapret2_common = require("providers.zapret2.common");
|
||||
let zapret2_validator = require("providers.zapret2.validator");
|
||||
|
||||
const LIB_DIR = getenv("TACHYON_LIB") || "/usr/lib/tachyon";
|
||||
const CONFIG_NAME = getenv("TACHYON_CONFIG_NAME") || constants.TACHYON_CONFIG_NAME || "tachyon";
|
||||
const CACHE_FILE = "/tmp/tachyon_zapret2_tuning_cache.json";
|
||||
|
||||
function as_string(value) {
|
||||
return value == null ? "" : "" + value;
|
||||
}
|
||||
|
||||
function trim(value) {
|
||||
value = as_string(value);
|
||||
let start = 0;
|
||||
let end = length(value);
|
||||
while (start < end && match(substr(value, start, 1), /[ \t\r\n]/) != null)
|
||||
start++;
|
||||
while (end > start && match(substr(value, end - 1, 1), /[ \t\r\n]/) != null)
|
||||
end--;
|
||||
return substr(value, start, end - start);
|
||||
}
|
||||
|
||||
function now_seconds() {
|
||||
return time();
|
||||
}
|
||||
|
||||
function write_json(value) {
|
||||
print(sprintf("%J", value), "\n");
|
||||
}
|
||||
|
||||
function read_json_file(path) {
|
||||
path = as_string(path);
|
||||
if (path == "" || fs.stat(path) == null)
|
||||
return null;
|
||||
let content = fs.readfile(path);
|
||||
if (content == null || content == "")
|
||||
return null;
|
||||
try {
|
||||
return json(content);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function write_json_file(path, data) {
|
||||
path = as_string(path);
|
||||
if (path == "")
|
||||
return false;
|
||||
let content = sprintf("%J", data);
|
||||
let tmp_path = path + ".tmp." + now_seconds();
|
||||
let file = fs.open(tmp_path, "w");
|
||||
if (!file)
|
||||
return false;
|
||||
file.write(content);
|
||||
file.close();
|
||||
return fs.rename(tmp_path, path);
|
||||
}
|
||||
|
||||
function shell_quote(value) {
|
||||
return "'" + replace(as_string(value), /'/g, "'\\''") + "'";
|
||||
}
|
||||
|
||||
function command_status(cmd) {
|
||||
let status = int(system(cmd + " >/dev/null 2>&1"));
|
||||
return status > 255 ? int(status / 256) : status;
|
||||
}
|
||||
|
||||
function command_output(cmd) {
|
||||
let pipe = fs.popen(cmd, "r");
|
||||
if (!pipe)
|
||||
return "";
|
||||
let data = pipe.read("all");
|
||||
pipe.close();
|
||||
return as_string(data);
|
||||
}
|
||||
|
||||
function get_section_domain(section_id) {
|
||||
section_id = as_string(section_id);
|
||||
if (section_id == "")
|
||||
return "";
|
||||
|
||||
let domain = uci_core.get(CONFIG_NAME, section_id, "domain");
|
||||
if (type(domain) == "array" && length(domain) > 0)
|
||||
return as_string(domain[0]);
|
||||
if (type(domain) == "string" && trim(domain) != "") {
|
||||
let parts = split(trim(domain), /[ \t\r\n]+/);
|
||||
if (length(parts) > 0)
|
||||
return parts[0];
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
function get_candidate_strategies(mode) {
|
||||
mode = as_string(mode || "express");
|
||||
|
||||
let base_http = "--filter-tcp=80 --filter-l7=http --payload=http_req ";
|
||||
let base_tls = "--new --filter-tcp=443 --filter-l7=tls --payload=tls_client_hello ";
|
||||
let base_quic = "--new --filter-udp=443 --filter-l7=quic --payload=quic_initial ";
|
||||
|
||||
let presets = [
|
||||
{
|
||||
id: "split2_basic",
|
||||
name: "Basic Split2",
|
||||
family: "split2",
|
||||
opt: base_http + "--lua-desync=split2:pos=method+2 " +
|
||||
base_tls + "--lua-desync=split2:pos=1,midsld " +
|
||||
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
|
||||
},
|
||||
{
|
||||
id: "disorder2_basic",
|
||||
name: "Basic Disorder2",
|
||||
family: "disorder2",
|
||||
opt: base_http + "--lua-desync=disorder2:pos=method+2 " +
|
||||
base_tls + "--lua-desync=disorder2:pos=1,midsld " +
|
||||
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
|
||||
},
|
||||
{
|
||||
id: "fake_split2_md5",
|
||||
name: "Fake + Split2 (MD5Sig)",
|
||||
family: "fake_split2",
|
||||
opt: base_http + "--lua-desync=fake:blob=fake_default_http:tcp_md5 --lua-desync=split2:pos=method+2 " +
|
||||
base_tls + "--lua-desync=fake:blob=fake_default_tls:tcp_md5:tcp_seq=-10000 --lua-desync=split2:pos=1,midsld " +
|
||||
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
|
||||
},
|
||||
{
|
||||
id: "fake_disorder2_md5",
|
||||
name: "Fake + Disorder2 (MD5Sig)",
|
||||
family: "fake_disorder2",
|
||||
opt: base_http + "--lua-desync=fake:blob=fake_default_http:tcp_md5 --lua-desync=disorder2:pos=method+2 " +
|
||||
base_tls + "--lua-desync=fake:blob=fake_default_tls:tcp_md5:tcp_seq=-10000 --lua-desync=multidisorder:pos=1,midsld " +
|
||||
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
|
||||
},
|
||||
{
|
||||
id: "multisplit_default",
|
||||
name: "Multisplit (Default Preset)",
|
||||
family: "multisplit",
|
||||
opt: base_http + "--lua-desync=fake:blob=fake_default_http:tcp_md5 --lua-desync=multisplit:pos=method+2 " +
|
||||
base_tls + "--lua-desync=fake:blob=fake_default_tls:tcp_md5:tcp_seq=-10000 --lua-desync=multidisorder:pos=1,midsld " +
|
||||
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
|
||||
},
|
||||
{
|
||||
id: "syndata_split",
|
||||
name: "Syndata + Split",
|
||||
family: "syndata",
|
||||
opt: base_http + "--lua-desync=syndata,split2:pos=method+2 " +
|
||||
base_tls + "--lua-desync=syndata,split2:pos=1,sni,midsld " +
|
||||
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
|
||||
},
|
||||
{
|
||||
id: "fakedsplit_tls",
|
||||
name: "Fakedsplit + Multidisorder",
|
||||
family: "fakedsplit",
|
||||
opt: base_http + "--lua-desync=fake:blob=fake_default_http:tcp_md5 --lua-desync=split2:pos=method+2 " +
|
||||
base_tls + "--lua-desync=fakedsplit:blob=fake_default_tls:pos=1,sni,midsld --lua-desync=multidisorder:pos=1,midsld " +
|
||||
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
|
||||
}
|
||||
];
|
||||
|
||||
if (mode == "deep") {
|
||||
push(presets, {
|
||||
id: "fake_split2_ttl",
|
||||
name: "Fake + Split2 (TTL Auto)",
|
||||
family: "fake_split2",
|
||||
opt: base_http + "--lua-desync=fake:blob=fake_default_http:ttl=3 --lua-desync=split2:pos=method+2 " +
|
||||
base_tls + "--lua-desync=fake:blob=fake_default_tls:ttl=3:tcp_seq=-10000 --lua-desync=split2:pos=1,sni " +
|
||||
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
|
||||
});
|
||||
push(presets, {
|
||||
id: "multisplit_sni_pos",
|
||||
name: "Multisplit (SNI Position)",
|
||||
family: "multisplit",
|
||||
opt: base_http + "--lua-desync=multisplit:pos=method+2 " +
|
||||
base_tls + "--lua-desync=multisplit:pos=1,sni,midsld " +
|
||||
base_quic + "--lua-desync=fake:blob=fake_default_quic:repeats=6"
|
||||
});
|
||||
}
|
||||
|
||||
return presets;
|
||||
}
|
||||
|
||||
function test_domain_probe(domain, opt) {
|
||||
domain = as_string(domain);
|
||||
if (domain == "")
|
||||
return { success: false, rtt_ms: 0, message: "Empty domain" };
|
||||
|
||||
let port = 443;
|
||||
let url = "https://" + domain + "/";
|
||||
|
||||
let start_time = now_seconds();
|
||||
|
||||
let cmd = sprintf(
|
||||
"curl -s -o /dev/null -w '%%{http_code}:%%{time_total}' --connect-timeout 2 --max-time 3 -I %s",
|
||||
shell_quote(url)
|
||||
);
|
||||
|
||||
let output = trim(command_output(cmd));
|
||||
let elapsed = int((now_seconds() - start_time) * 1000);
|
||||
|
||||
if (output != "") {
|
||||
let parts = split(output, ":");
|
||||
if (length(parts) >= 2) {
|
||||
let code = int(parts[0]);
|
||||
let time_total = (1.0 * parts[1]) * 1000.0;
|
||||
if (code > 0 && code < 550) {
|
||||
return {
|
||||
success: true,
|
||||
http_code: code,
|
||||
rtt_ms: int(time_total > 0 ? time_total : elapsed),
|
||||
message: "HTTP " + code
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let nc_cmd = sprintf("nc -w 2 -z %s %d", shell_quote(domain), port);
|
||||
if (command_status(nc_cmd) == 0) {
|
||||
return {
|
||||
success: true,
|
||||
http_code: 200,
|
||||
rtt_ms: elapsed,
|
||||
message: "TCP Port 443 Open"
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
http_code: 0,
|
||||
rtt_ms: elapsed,
|
||||
message: "Connection failed or timed out"
|
||||
};
|
||||
}
|
||||
|
||||
function update_job_progress(job_path, completed, total, failed, current_item) {
|
||||
if (job_path == "" || fs.stat(job_path) == null)
|
||||
return;
|
||||
let data = read_json_file(job_path);
|
||||
if (type(data) != "object")
|
||||
return;
|
||||
|
||||
data.progress = {
|
||||
completed: int(completed),
|
||||
total: int(total),
|
||||
failed: int(failed),
|
||||
current_item: as_string(current_item)
|
||||
};
|
||||
data.updated_at = now_seconds();
|
||||
write_json_file(job_path, data);
|
||||
}
|
||||
|
||||
function run_tune(section_id, target_domain, mode, job_path) {
|
||||
section_id = as_string(section_id);
|
||||
target_domain = as_string(target_domain);
|
||||
mode = as_string(mode || "express");
|
||||
job_path = as_string(job_path);
|
||||
|
||||
if (target_domain == "")
|
||||
target_domain = get_section_domain(section_id);
|
||||
if (target_domain == "")
|
||||
target_domain = "googlevideo.com";
|
||||
|
||||
let candidates = get_candidate_strategies(mode);
|
||||
let total = length(candidates);
|
||||
let results = [];
|
||||
let best_candidate = null;
|
||||
let best_rtt = 999999;
|
||||
let failed_count = 0;
|
||||
|
||||
let failed_families = {};
|
||||
|
||||
for (let i = 0; i < total; i++) {
|
||||
let cand = candidates[i];
|
||||
let family = cand.family;
|
||||
|
||||
if (failed_families[family] === true && mode == "express") {
|
||||
push(results, {
|
||||
id: cand.id,
|
||||
name: cand.name,
|
||||
family: cand.family,
|
||||
opt: cand.opt,
|
||||
success: false,
|
||||
skipped: true,
|
||||
rtt_ms: 0,
|
||||
message: "Skipped (Family pruned)"
|
||||
});
|
||||
failed_count++;
|
||||
update_job_progress(job_path, i + 1, total, failed_count, cand.name);
|
||||
continue;
|
||||
}
|
||||
|
||||
update_job_progress(job_path, i, total, failed_count, cand.name);
|
||||
|
||||
let validation = zapret2_validator.validate_strategy("nfqws2", cand.opt, "");
|
||||
if (!validation.valid) {
|
||||
push(results, {
|
||||
id: cand.id,
|
||||
name: cand.name,
|
||||
family: cand.family,
|
||||
opt: cand.opt,
|
||||
success: false,
|
||||
rtt_ms: 0,
|
||||
message: "Validation error: " + as_string(validation.message)
|
||||
});
|
||||
failed_count++;
|
||||
failed_families[family] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
let probe = test_domain_probe(target_domain, cand.opt);
|
||||
if (probe.success) {
|
||||
push(results, {
|
||||
id: cand.id,
|
||||
name: cand.name,
|
||||
family: cand.family,
|
||||
opt: cand.opt,
|
||||
success: true,
|
||||
http_code: probe.http_code,
|
||||
rtt_ms: probe.rtt_ms,
|
||||
message: probe.message
|
||||
});
|
||||
|
||||
if (probe.rtt_ms < best_rtt) {
|
||||
best_rtt = probe.rtt_ms;
|
||||
best_candidate = cand;
|
||||
}
|
||||
} else {
|
||||
push(results, {
|
||||
id: cand.id,
|
||||
name: cand.name,
|
||||
family: cand.family,
|
||||
opt: cand.opt,
|
||||
success: false,
|
||||
rtt_ms: probe.rtt_ms,
|
||||
message: probe.message
|
||||
});
|
||||
failed_count++;
|
||||
failed_families[family] = true;
|
||||
}
|
||||
|
||||
update_job_progress(job_path, i + 1, total, failed_count, cand.name);
|
||||
}
|
||||
|
||||
if (best_candidate == null && length(results) > 0) {
|
||||
best_candidate = candidates[0];
|
||||
}
|
||||
|
||||
let output = {
|
||||
success: true,
|
||||
section_id,
|
||||
target_domain,
|
||||
mode,
|
||||
winning_strategy: best_candidate ? best_candidate.opt : "",
|
||||
winning_preset_id: best_candidate ? best_candidate.id : "",
|
||||
winning_preset_name: best_candidate ? best_candidate.name : "",
|
||||
best_rtt_ms: best_rtt == 999999 ? 0 : best_rtt,
|
||||
tested_count: total,
|
||||
results
|
||||
};
|
||||
|
||||
let cache_data = read_json_file(CACHE_FILE) || {};
|
||||
if (type(cache_data) != "object")
|
||||
cache_data = {};
|
||||
cache_data[target_domain] = {
|
||||
updated_at: now_seconds(),
|
||||
winning_strategy: output.winning_strategy,
|
||||
best_rtt_ms: output.best_rtt_ms
|
||||
};
|
||||
write_json_file(CACHE_FILE, cache_data);
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
function main(argv) {
|
||||
let mode_cmd = argv[0] || "tune";
|
||||
|
||||
if (mode_cmd == "tune" || mode_cmd == "run") {
|
||||
let section_id = argv[1] || "";
|
||||
let target_domain = argv[2] || "";
|
||||
let mode = argv[3] || "express";
|
||||
let job_path = argv[4] || "";
|
||||
|
||||
let res = run_tune(section_id, target_domain, mode, job_path);
|
||||
write_json(res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (mode_cmd == "candidates") {
|
||||
let mode = argv[1] || "express";
|
||||
write_json({
|
||||
success: true,
|
||||
mode,
|
||||
candidates: get_candidate_strategies(mode)
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
write_json({
|
||||
success: false,
|
||||
message: "Usage: providers/zapret2/tune.uc <tune|candidates> [section_id] [target_domain] [mode] [job_path]"
|
||||
});
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (sourcepath(1) != null && sourcepath(1) != "") {
|
||||
return {
|
||||
get_candidate_strategies,
|
||||
run_tune,
|
||||
test_domain_probe
|
||||
};
|
||||
}
|
||||
|
||||
return main(ARGV);
|
||||
@@ -17,6 +17,7 @@ const SERVICE_ACTION_LOCK_DIR = getenv("TACHYON_UI_SERVICE_ACTION_LOCK_DIR") ||
|
||||
const LATENCY_ACTION_DIR = getenv("TACHYON_UI_LATENCY_ACTION_DIR") || STATE_DIR + "/latency-actions";
|
||||
const COMPONENT_ACTION_DIR = getenv("TACHYON_UI_COMPONENT_ACTION_DIR") || getenv("UPDATES_JOB_DIR") || "/var/run/tachyon/component-actions";
|
||||
const SUBSCRIPTION_ACTION_DIR = getenv("TACHYON_UI_SUBSCRIPTION_ACTION_DIR") || getenv("TACHYON_SUBSCRIPTION_UPDATE_JOB_DIR") || "/var/run/tachyon/subscription-update-jobs";
|
||||
const ZAPRET2_TUNE_ACTION_DIR = getenv("TACHYON_UI_ZAPRET2_TUNE_ACTION_DIR") || STATE_DIR + "/zapret2-tune-actions";
|
||||
const SING_BOX_VERSION_CACHE_FILE = getenv("TACHYON_UI_SING_BOX_VERSION_CACHE_FILE") || STATE_DIR + "/sing-box-version";
|
||||
const SING_BOX_VERSION_CACHE_LOCK_DIR = getenv("TACHYON_UI_SING_BOX_VERSION_CACHE_LOCK_DIR") || SING_BOX_VERSION_CACHE_FILE + ".lock";
|
||||
const SING_BOX_VARIANT_STATE_FILE = getenv("TACHYON_UI_SING_BOX_VARIANT_STATE_FILE") || "/etc/tachyon/sing-box-variant";
|
||||
@@ -1476,6 +1477,77 @@ function latency_test_status(job_id_value) {
|
||||
print(as_string(fs.readfile(path)));
|
||||
}
|
||||
|
||||
function zapret2_tune_worker(path, section_id, target_domain, tune_mode) {
|
||||
let tune_module = LIB_DIR + "/providers/zapret2/tune.uc";
|
||||
let cmd = command_from_args([ "ucode", "-L", LIB_DIR, tune_module, "tune", section_id, target_domain, tune_mode, path ]);
|
||||
let status = command_status(cmd + " >/dev/null 2>&1");
|
||||
if (status == 0)
|
||||
write_finished_action_state(path, true, "zapret2 strategy tuning completed", status);
|
||||
else
|
||||
write_finished_action_state(path, false, "zapret2 strategy tuning failed", status);
|
||||
}
|
||||
|
||||
function running_zapret2_tune_action_value(section_id, target_domain, tune_mode, started_at) {
|
||||
return {
|
||||
success: true,
|
||||
running: true,
|
||||
kind: "zapret2_tune",
|
||||
section: as_string(section_id),
|
||||
target_domain: as_string(target_domain),
|
||||
tune_mode: as_string(tune_mode),
|
||||
message: "zapret2 strategy tuning is running",
|
||||
pid: null,
|
||||
started_at: arg_number(started_at),
|
||||
updated_at: null,
|
||||
exit_code: null,
|
||||
progress: {
|
||||
completed: 0,
|
||||
total: 7,
|
||||
failed: 0,
|
||||
current_item: "Initializing..."
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function zapret2_tune_async(section_id, target_domain, tune_mode) {
|
||||
section_id = as_string(section_id);
|
||||
target_domain = as_string(target_domain);
|
||||
tune_mode = as_string(tune_mode || "express");
|
||||
|
||||
ensure_dirs();
|
||||
let id = job_id();
|
||||
let path = job_state_path_value(ZAPRET2_TUNE_ACTION_DIR, id);
|
||||
if (path == "" || !write_state_file(path, running_zapret2_tune_action_value(section_id, target_domain, tune_mode, now_seconds()))) {
|
||||
action_start_response(false, "", "Failed to write zapret2 tune action state");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
let pid = launch_worker([ "zapret2-tune-worker", path, section_id, target_domain, tune_mode ]);
|
||||
if (pid == "" || !set_running_job_pid_file(path, pid)) {
|
||||
if (pid != "")
|
||||
command_success_from_args([ "kill", pid ]);
|
||||
action_start_response(false, "", "Failed to write zapret2 tune worker pid");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
action_start_response(true, id, "zapret2 strategy tuning started");
|
||||
}
|
||||
|
||||
function zapret2_tune_status(job_id_value) {
|
||||
let path = job_state_path_value(ZAPRET2_TUNE_ACTION_DIR, job_id_value);
|
||||
if (path == "") {
|
||||
action_start_response(false, "", "Invalid zapret2 tune job id");
|
||||
exit(1);
|
||||
}
|
||||
if (fs.stat(path) == null) {
|
||||
action_start_response(false, "", "zapret2 tune job was not found");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
refresh_pid_job_state(path, "zapret2 tune worker exited unexpectedly");
|
||||
print(as_string(fs.readfile(path)));
|
||||
}
|
||||
|
||||
function action_dir(kind) {
|
||||
kind = as_string(kind);
|
||||
if (kind == "service")
|
||||
@@ -1486,6 +1558,8 @@ function action_dir(kind) {
|
||||
return COMPONENT_ACTION_DIR;
|
||||
if (kind == "subscription")
|
||||
return SUBSCRIPTION_ACTION_DIR;
|
||||
if (kind == "zapret2_tune")
|
||||
return ZAPRET2_TUNE_ACTION_DIR;
|
||||
return "";
|
||||
}
|
||||
|
||||
@@ -1587,6 +1661,12 @@ else if (mode == "latency-test-async")
|
||||
latency_test_async(ARGV[1], ARGV[2], ARGV[3], ARGV[4]);
|
||||
else if (mode == "latency-test-status")
|
||||
latency_test_status(ARGV[1]);
|
||||
else if (mode == "zapret2-tune-worker")
|
||||
zapret2_tune_worker(ARGV[1], ARGV[2], ARGV[3], ARGV[4]);
|
||||
else if (mode == "zapret2-tune-async")
|
||||
zapret2_tune_async(ARGV[1], ARGV[2], ARGV[3]);
|
||||
else if (mode == "zapret2-tune-status")
|
||||
zapret2_tune_status(ARGV[1]);
|
||||
else if (mode == "action-ack")
|
||||
action_ack(ARGV[1], ARGV[2]);
|
||||
else if (mode == "cleanup-action-dir-fixture")
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
set -eo pipefail
|
||||
|
||||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
TACHYON_LIB="$ROOT_DIR/tachyon/files/usr/lib"
|
||||
TUNE_MODULE="$ROOT_DIR/tachyon/files/usr/lib/providers/zapret2/tune.uc"
|
||||
CLI="$ROOT_DIR/tachyon/files/usr/bin/tachyon"
|
||||
WORK_DIR="$(mktemp -d)"
|
||||
|
||||
cleanup() {
|
||||
rm -rf "$WORK_DIR"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
fail() {
|
||||
printf 'FAIL: %s\n' "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
json_field() {
|
||||
local json="$1"
|
||||
local field="$2"
|
||||
|
||||
JSON_VALUE="$json" node - "$field" <<'NODE'
|
||||
const field = process.argv[2];
|
||||
const value = JSON.parse(process.env.JSON_VALUE);
|
||||
const actual = value[field];
|
||||
if (Array.isArray(actual)) {
|
||||
console.log(actual.join("\n"));
|
||||
} else {
|
||||
console.log(String(actual));
|
||||
}
|
||||
NODE
|
||||
}
|
||||
|
||||
assert_json_field() {
|
||||
local json="$1"
|
||||
local field="$2"
|
||||
local expected="$3"
|
||||
local actual
|
||||
|
||||
actual="$(json_field "$json" "$field")"
|
||||
[ "$actual" = "$expected" ] || fail "expected $field=$expected, got $actual"
|
||||
}
|
||||
|
||||
# 1. Test candidates generation
|
||||
candidates="$(ucode -L "$TACHYON_LIB" "$TUNE_MODULE" candidates express)"
|
||||
assert_json_field "$candidates" success true
|
||||
assert_json_field "$candidates" mode express
|
||||
|
||||
# 2. Test tuning run on test domain
|
||||
tune_result="$(ucode -L "$TACHYON_LIB" "$TUNE_MODULE" tune "" "example.com" express)"
|
||||
assert_json_field "$tune_result" success true
|
||||
assert_json_field "$tune_result" target_domain "example.com"
|
||||
|
||||
# 3. Test CLI zapret2_tune entrypoint
|
||||
cli_result="$(TACHYON_LIB="$TACHYON_LIB" ucode -L "$TACHYON_LIB" "$CLI" zapret2_tune "" "example.com" express)"
|
||||
assert_json_field "$cli_result" success true
|
||||
|
||||
printf 'zapret2 tuner tests passed successfully\n'
|
||||
Reference in New Issue
Block a user