mirror of
https://github.com/YuanDevTeam/Vole.git
synced 2026-11-07 00:05:27 +00:00
Add provider generations, automatic proxy groups, richer routing rules and live Controller configuration. Extend encrypted DNS, persistent fake-IP mappings and GeoData support. Add HTTP CONNECT, WireGuard, TrustTunnel, Mieru and MASQUE outbounds; expand TLS/ECH and transport compatibility. Add separately configured H1/H2 MITM and URL, header and body rewriting. Improve UDP association reuse, TCP delivery and H2/H3 scheduling while keeping memory bounded. Add Linux TUN route and system DNS management, MPTCP and TCP Brutal. Replace vendored dependencies with upstream libraries, update and pin Boring 5.4.0, and fix cross-platform builds and CI feature checks.
386 lines
24 KiB
YAML
386 lines
24 KiB
YAML
name: Tests
|
|
|
|
on:
|
|
pull_request:
|
|
push:
|
|
branches: [main]
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
|
|
env:
|
|
UV_PYTHON_PREFERENCE: only-system
|
|
UV_PYTHON_DOWNLOADS: never
|
|
|
|
jobs:
|
|
client-contracts:
|
|
name: ${{ matrix.name }}
|
|
runs-on: ubuntu-26.04
|
|
timeout-minutes: 90
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- name: Core (Debug)
|
|
profile: debug
|
|
- name: Core (Release)
|
|
profile: release
|
|
- name: Quality
|
|
profile: features
|
|
steps:
|
|
- uses: actions/checkout@v7
|
|
- uses: dtolnay/rust-toolchain@stable
|
|
with:
|
|
toolchain: stable
|
|
components: rustfmt, clippy
|
|
- uses: actions/setup-python@v7
|
|
with:
|
|
python-version: "3.x"
|
|
check-latest: true
|
|
- uses: astral-sh/setup-uv@v10.3.3
|
|
with:
|
|
version: "0.13.6"
|
|
- name: Linux native dependencies
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y clang libclang-dev cmake ninja-build perl pkg-config
|
|
- name: Prepare official BLAKE3 static dependency
|
|
shell: bash
|
|
run: uv run --project scripts --locked vole-scripts deps --github-env "$GITHUB_ENV"
|
|
- name: Cache Rust dependencies
|
|
uses: Swatinem/rust-cache@v2
|
|
with:
|
|
key: tests-${{ matrix.profile }}-${{ hashFiles('scripts/native/**', 'scripts/src/vole_scripts/*.py') }}
|
|
workspaces: ". -> target"
|
|
# Native TLS artifacts must not cross runner SDK/image changes.
|
|
env-vars: ImageOS ImageVersion SDKROOT DEVELOPER_DIR BORING AWS_LC LIBCLANG BINDGEN VOLE_BLAKE3
|
|
cache-bin: "false"
|
|
- name: Format and scripts
|
|
if: matrix.profile == 'features'
|
|
run: |
|
|
cargo fmt --all -- --check
|
|
uv run --project scripts --locked python -m unittest discover -s scripts/tests
|
|
uv run --project scripts --locked ruff check scripts
|
|
uv run --project scripts --locked ruff format --check scripts
|
|
- name: Compile the public C and C++ header
|
|
if: matrix.profile == 'features'
|
|
shell: bash
|
|
run: |
|
|
clang -x c -std=c11 -Wall -Wextra -Werror -fsyntax-only -include vole.h -I include - <<'C'
|
|
int main(void) {
|
|
char *response = VoleInvoke("{\"method\":\"version\",\"payload\":{}}");
|
|
VoleFree(response);
|
|
VoleFree((char *)0);
|
|
return 0;
|
|
}
|
|
C
|
|
clang++ -x c++ -std=c++17 -Wall -Wextra -Werror -fsyntax-only -include vole.h -I include - <<'CPP'
|
|
int main() {
|
|
char *response = VoleInvoke("{\"method\":\"version\",\"payload\":{}}");
|
|
VoleFree(response);
|
|
VoleFree(nullptr);
|
|
return 0;
|
|
}
|
|
CPP
|
|
- name: Audit the locked TLS dependency graph
|
|
if: matrix.profile == 'features'
|
|
shell: bash
|
|
run: |
|
|
cargo metadata --locked --all-features --format-version 1 > "$RUNNER_TEMP/vole-metadata.json"
|
|
uv run --project scripts --locked python - "$RUNNER_TEMP/vole-metadata.json" <<'PY'
|
|
import json
|
|
import re
|
|
import sys
|
|
import tomllib
|
|
from pathlib import Path
|
|
|
|
metadata = json.loads(Path(sys.argv[1]).read_text(encoding="utf-8"))
|
|
packages = metadata["packages"]
|
|
nodes = {node["id"]: node for node in metadata["resolve"]["nodes"]}
|
|
registry = {"registry+https://github.com/rust-lang/crates.io-index", "registry+https://index.crates.io/"}
|
|
smoltcp_source = "git+https://github.com/smoltcp-rs/smoltcp?rev=24bda3b4f982625d8453d4360172557d24921c76#24bda3b4f982625d8453d4360172557d24921c76"
|
|
|
|
def require(condition, message):
|
|
if not condition:
|
|
raise SystemExit(message)
|
|
|
|
def package(name, version=None, sources=registry):
|
|
matches = [item for item in packages if item["name"] == name]
|
|
require(len(matches) == 1, f"expected one resolved {name}")
|
|
item = matches[0]
|
|
require(version is None or item["version"] == version, f"unapproved {name} version")
|
|
require(item.get("source") in sources, f"unapproved {name} source")
|
|
require(item["id"] in nodes, f"{name} missing from resolved graph")
|
|
return item
|
|
|
|
def features(item):
|
|
return set(nodes[item["id"]]["features"])
|
|
|
|
require(not any("watfaq" in (item.get("source") or "").lower() for item in packages), "retired dependency source is forbidden")
|
|
workspace_root = Path(metadata["workspace_root"])
|
|
manifest = tomllib.loads((workspace_root / "Cargo.toml").read_text(encoding="utf-8"))
|
|
boring_repository = "https://github.com/YuanDevTeam/boring"
|
|
boring_revision = manifest["dependencies"]["boring"].get("rev")
|
|
require(isinstance(boring_revision, str) and re.fullmatch(r"[0-9a-f]{40}", boring_revision), "Boring PR dependencies must pin a complete commit SHA")
|
|
boring_specs = [manifest["dependencies"][name] for name in ("boring", "tokio-boring")]
|
|
boring_specs.append(manifest["dev-dependencies"]["boring-sys"])
|
|
boring_specs.extend(manifest["patch"]["crates-io"][name] for name in ("boring", "boring-sys"))
|
|
for spec in boring_specs:
|
|
require(spec.get("git") == boring_repository and spec.get("rev") == boring_revision and not {"path", "branch", "tag"} & spec.keys(), "Boring dependencies and patches must share one Git commit source")
|
|
boring_source = f"git+{boring_repository}?rev={boring_revision}#{boring_revision}"
|
|
require(not (workspace_root / "vendor").exists(), "Core vendor sources are forbidden")
|
|
require(not (workspace_root / "crates" / "vole-blake3-raw" / "vendor").exists(), "BLAKE3 must use the installed official C library")
|
|
native_manifest = json.loads((workspace_root / "scripts" / "native" / "blake3" / "vcpkg.json").read_text(encoding="utf-8"))
|
|
require(native_manifest.get("builtin-baseline") == "9eec002df1cd109f8f104cd966a763daf9751489", "unapproved official BLAKE3 C registry baseline")
|
|
require(native_manifest.get("dependencies") == [{"name": "blake3", "default-features": False}], "BLAKE3 C must use the reviewed static-only package")
|
|
require(native_manifest.get("overrides") == [{"name": "blake3", "version": "1.8.7", "port-version": 0}], "unapproved official BLAKE3 C version")
|
|
for name, version in (("idna", "1.1.0"), ("url", "2.5.8")):
|
|
require(package(name, version).get("license") == "MIT OR Apache-2.0", f"unapproved {name} license")
|
|
for item in packages:
|
|
if item["name"].startswith(("icu_", "unicode-")):
|
|
require(item.get("source") in registry, f"{item['name']} must use official registry Unicode data")
|
|
rust_blake3 = package("blake3", "1.8.7")
|
|
require(rust_blake3.get("license") == "CC0-1.0 OR Apache-2.0 OR Apache-2.0 WITH LLVM-exception", "unapproved Rust BLAKE3 license")
|
|
rustls = package("rustls", "0.23.46")
|
|
package("tokio-rustls", "0.26.6")
|
|
package("rustls-native-certs", "0.8.5")
|
|
ring = package("ring")
|
|
quinn = package("quinn", "0.11.12")
|
|
proto = package("quinn-proto", "0.11.19")
|
|
require(proto.get("license") == "MIT OR Apache-2.0", "unapproved QUIC protocol license")
|
|
require({"rustls-ring", "ring"} <= features(proto), "QUIC protocol must use rustls/ring")
|
|
require("rustls-ring" in features(quinn), "Quinn wrapper must use rustls/ring")
|
|
require(not (features(proto) | features(quinn)) & {"aws-lc-rs", "aws-lc-rs-fips", "rustls-aws-lc-rs", "rustls-aws-lc-rs-fips"}, "Quinn must keep the approved ring TLS provider")
|
|
require(proto["id"] in {dependency["pkg"] for dependency in nodes[quinn["id"]]["deps"]}, "Quinn wrapper must resolve the official protocol source")
|
|
h3 = package("h3", "0.0.8")
|
|
require(h3.get("license") == "MIT", "unapproved Quinn HTTP/3 license")
|
|
h3_quinn = package("h3-quinn", "0.0.10")
|
|
require(h3["id"] in {dependency["pkg"] for dependency in nodes[h3_quinn["id"]]["deps"]}, "Quinn HTTP/3 adapter must resolve the official source")
|
|
quiche = package("quiche", "0.30.0")
|
|
require(features(quiche) == {"boring", "boring-sys", "boringssl-boring-crate", "foreign-types-shared"}, "quiche must use the shared Boring provider without FFI or qlog")
|
|
require(quiche.get("license") == "BSD-2-Clause", "unapproved quiche license")
|
|
stack = package("smoltcp", "0.14.0", {smoltcp_source})
|
|
require(stack.get("license") == "0BSD", "unapproved raw IP stack license")
|
|
rsa = package("rsa", "0.9.10")
|
|
rcgen = package("rcgen", "0.14.10")
|
|
require(features(rsa) == {"std"}, "RSA is restricted to controller key generation")
|
|
require(features(rcgen) == {"crypto", "ring", "x509-parser"}, "certificate generation must use ring and the reviewed CA parser")
|
|
x509 = package("x509-parser", "0.18.1")
|
|
require(x509.get("license") == "MIT OR Apache-2.0", "unapproved X.509 parser license")
|
|
require(features(x509) == {"default", "ring", "verify"}, "X.509 verification must use ring")
|
|
x509_parents = {node["id"] for node in nodes.values() if any(dependency["pkg"] == x509["id"] for dependency in node.get("deps", []))}
|
|
require(x509_parents == {metadata["resolve"]["root"], rcgen["id"]}, "X.509 parsing is restricted to Vole and rcgen")
|
|
for item in (rsa, rcgen):
|
|
require(item.get("license") == "MIT OR Apache-2.0", f"unapproved {item['name']} license")
|
|
parents = {node["id"] for node in nodes.values() if any(dependency["pkg"] == item["id"] for dependency in node.get("deps", []))}
|
|
require(parents == {metadata["resolve"]["root"]}, f"{item['name']} must be consumed only by Vole")
|
|
require(ring["id"] in {dependency["pkg"] for dependency in nodes[rcgen["id"]]["deps"]}, "rcgen must use the approved ring package")
|
|
wireguard = package("boringtun", "0.7.1")
|
|
require(wireguard.get("license") == "BSD-3-Clause", "unapproved WireGuard license")
|
|
require(not features(wireguard) & {"device", "ffi-bindings", "jni-bindings"}, "WireGuard must use only the userspace protocol")
|
|
require("ring" in features(rustls) and not features(rustls) & {"reality", "aws_lc_rs", "fips"}, "rustls must use only the ring TLS provider")
|
|
mieru_aead = next(item for item in packages if item["name"] == "chacha20poly1305" and item["version"] == "0.11.0")
|
|
require(mieru_aead.get("source") in registry and "zeroize" in features(mieru_aead), "Mieru must use official zeroizing XChaCha20-Poly1305")
|
|
mieru_proto = package("prost", "0.14.4")
|
|
require(mieru_proto.get("license") == "Apache-2.0" and mieru_proto.get("source") in registry, "Mieru protobuf must use official Apache-2.0 prost")
|
|
hpke = package("hpke", "0.15.1")
|
|
require(features(hpke) == {"alloc", "aes", "chacha", "x25519", "hkdfsha2", "nistp", "mlkem", "shake"}, "unapproved HPKE algorithms")
|
|
age = package("age", "0.11.5")
|
|
require(features(age) == {"armor"}, "AGE must use only native armored-file decoding")
|
|
package("age-core", "0.11.0")
|
|
package("x-wing", "0.1.1")
|
|
package("ml-kem", "0.3.4")
|
|
native = {}
|
|
for name, required in (("boring", {"reality", "client-fingerprint", "shadow-tls-v3", "jls"}), ("boring-sys", {"reality", "shadow-tls-v3", "jls"}), ("tokio-boring", set())):
|
|
item = package(name, "5.4.0", {boring_source})
|
|
native[name] = item
|
|
require(required <= features(item), f"{name} is missing required TLS features")
|
|
require(not features(item) & {"fips", "fips-precompiled", "rpk", "pq-experimental", "restls"}, f"{name} enables an unapproved TLS mode")
|
|
for parent, children in (("boring", {"boring-sys"}), ("tokio-boring", {"boring", "boring-sys"})):
|
|
dependencies = {dependency["pkg"] for dependency in nodes[native[parent]["id"]]["deps"]}
|
|
require(all(native[child]["id"] in dependencies for child in children), f"{parent} must use the approved native TLS packages")
|
|
require(native["boring"]["id"] in {dependency["pkg"] for dependency in nodes[quiche["id"]]["deps"]}, "quiche must share Vole's approved Boring package")
|
|
aws = [item for item in packages if item["name"].lower().startswith("aws-lc")]
|
|
if aws:
|
|
require({item["name"] for item in aws} == {"aws-lc-rs", "aws-lc-sys"}, "unapproved AWS-LC package")
|
|
chain = {name: package(name, version) for name, version in (("shadowsocks", "1.25.0"), ("shadowsocks-crypto", "0.8.0"), ("aws-lc-rs", None), ("aws-lc-sys", None))}
|
|
parents = {}
|
|
for node in nodes.values():
|
|
for dependency in node.get("deps", []):
|
|
parents.setdefault(dependency["pkg"], set()).add(node["id"])
|
|
for child, parent in (("shadowsocks-crypto", "shadowsocks"), ("aws-lc-rs", "shadowsocks-crypto"), ("aws-lc-sys", "aws-lc-rs")):
|
|
require(parents.get(chain[child]["id"]) == {chain[parent]["id"]}, f"{child} must be consumed only by {parent}")
|
|
for name, required in (("shadowsocks", {"aead-cipher-2022"}), ("shadowsocks-crypto", {"v2", "aws-lc"})):
|
|
require(required <= features(chain[name]), f"{name} must enable SS 2022")
|
|
require(not any(features(item) & {"fips", "aead-cipher-2022-extra", "v2-extra"} for item in chain.values()), "unapproved SS 2022 or AWS-LC mode")
|
|
print("Locked TLS/Unicode/BLAKE3 sources, controller generation, HPKE/AGE algorithms and SS 2022 dependency chain passed")
|
|
PY
|
|
- name: Official BLAKE3 raw memory vectors
|
|
if: matrix.profile == 'features'
|
|
run: |
|
|
cargo test --locked -p vole-blake3-raw
|
|
- name: H3 memory fixtures
|
|
if: matrix.profile == 'features'
|
|
run: |
|
|
cargo test --locked --features cli --lib transport::quiche_h3::tests::
|
|
cargo test --locked --features cli --lib transport::quinn_h3::tests::
|
|
- name: Production and netstack clippy
|
|
if: matrix.profile == 'features'
|
|
run: |
|
|
cargo clippy --locked --features ffi --lib --bins -- -D warnings
|
|
cargo clippy --locked --no-default-features --features cli --lib --bin vole -- -D warnings
|
|
cargo clippy --locked --all-features --lib --bins -- -D warnings
|
|
cargo clippy --locked --manifest-path crates/vole-netstack/Cargo.toml --all-targets -- -D warnings
|
|
- name: Run memory-only ${{ matrix.name }}
|
|
if: matrix.profile != 'features'
|
|
shell: bash
|
|
env:
|
|
CORE_TEST_PROFILE: ${{ matrix.profile }}
|
|
run: |
|
|
set -euo pipefail
|
|
test_command=(cargo test --locked --all-features)
|
|
# Keep release semantics without production LTO for each test binary.
|
|
if [[ "$CORE_TEST_PROFILE" == release ]]; then test_command+=(--profile ci-release); fi
|
|
run_test() {
|
|
local expected_summaries="$1"
|
|
shift
|
|
local test_log
|
|
test_log="$(mktemp "$RUNNER_TEMP/vole-tests.XXXXXX")"
|
|
"${test_command[@]}" "$@" | tee "$test_log"
|
|
test "$(grep -Ec '^test result: ok\. [0-9]+ passed; [0-9]+ failed; [0-9]+ ignored;' "$test_log")" -eq "$expected_summaries"
|
|
test "$(grep -Ec '^test result: ok\. [1-9][0-9]* passed; 0 failed;' "$test_log")" -eq "$expected_summaries"
|
|
rm -f "$test_log"
|
|
}
|
|
targets=(
|
|
ech_config ech_tls encryption_config reality_config jls_config
|
|
security_capabilities retired_security feature_foundations limit_foundations
|
|
trojan trojan_config vmess_config vless_config vless_lifecycle
|
|
xhttp_config xhttp_budget h2_stream_regression stream_foundations stream_shutdown
|
|
xhttp_h3_shutdown hysteria2_paths hysteria2_config shadowsocks_backpressure
|
|
shadowtls_config shadowtls_stream uot_config shadowsocks_uot hysteria2_packet_ids
|
|
tuic_config tuic_memory httpupgrade_config httpupgrade_memory vmess_lifecycle
|
|
grpc_config grpc_pool grpc_protocol_pool
|
|
sing_mux_config sing_mux
|
|
)
|
|
target_flags=()
|
|
for target in "${targets[@]}"; do target_flags+=(--test "$target"); done
|
|
run_test "${#targets[@]}" "${target_flags[@]}"
|
|
# Keep this explicit allowlist: unfiltered legacy tests may open host listeners.
|
|
filters=(
|
|
geodata:: routing:: security:: outbound::vless::encryption config::
|
|
connections::tests:: routing::diagnostics::tests:: dialer::connections::tests::
|
|
polled_datagram_
|
|
resource::http::tests:: fake_ip_ sniffing::tests:: tcp_sniffer::tests::
|
|
resource::age_provider::tests:: resource::bundle::tests:: resource::bundle_limits::tests::
|
|
resource::cache::tests:: resource::subscription::tests::
|
|
dns::fallback::tests:: dns::runtime::tests::dns_fallback_
|
|
dns::runtime::tests::resolve_ipv6_
|
|
dns::source::tests:: dns::runtime::tests::dns_source_
|
|
dns::runtime::encrypted_tests::
|
|
dns::options::tests:: dns::runtime::quic_tests:: dns::runtime::ech_tests::
|
|
controller_secure_
|
|
outbound::http::tests:: outbound::wireguard::tests:: inbound::mixed::tests:: inbound::socks5::association::tests::
|
|
inbound::http::parser::tests:: inbound::http::framing::tests::
|
|
outbound::hysteria2:: outbound::shadowsocks::tests::official_tcp_
|
|
outbound::anytls:: outbound::uot:: outbound::tuic:: transport::h2_write::tests::
|
|
outbound::trusttunnel:: outbound::mieru:: outbound::masque:: security::masque:: config::masque::
|
|
transport::quic:: transport::quinn_h3:: transport::quiche_h3:: transport::masque_codec:: transport::h3_connect::
|
|
transport::smux::tests:: transport::sing_mux::tests::
|
|
grpc_duplex_pool_keeps_response
|
|
outbound::sing_mux::tests::
|
|
platform::linux_network::tests:: platform::tun_rs_io::tests:: platform::windows_tun_io::tests::
|
|
platform::windows_wintun_io::tests:: tun_runtime::tests::
|
|
invoke::foreground::tests:: runtime::shutdown_tests:: runtime::providers::tests:: resource::probe::tests:: invoke::measure_delay::tests::
|
|
)
|
|
for filter in "${filters[@]}"; do run_test 1 --lib "$filter"; done
|
|
run_test 1 --bin vole cli::tests::
|
|
exact_tests=(
|
|
dialer::tests::socket_initialization_churn_reclaims_completed_work
|
|
dialer::tests::socket_initialization_cancellation_joins_started_and_queued_work
|
|
dialer::tests::socket_initialization_shared_pool_contention_reclaims_cancelled_work
|
|
outbound::connector::tests::authenticated_continuation_keeps_group_choice_but_has_a_new_io_deadline
|
|
outbound::connector::tests::ingress_connector_dispatcher_preserves_user_for_dialer_proxy_and_continuations
|
|
runtime::tests::integration_feature_admission_is_explicit_without_opening_sockets
|
|
invoke::tests::version_and_state_use_the_fixed_response_envelope
|
|
invoke::tests::initialize_is_idempotent_only_for_the_same_data_directory
|
|
invoke::tests::envelope_and_payload_are_strict
|
|
invoke::tests::runtime_thread_cannot_reenter_invoke
|
|
invoke::tests::engine_workers_preserve_invoke_admission_and_shutdown
|
|
invoke::tests::update_dns_network_raw_admission_is_bounded_before_value_or_registry_work
|
|
invoke::tests::update_dns_network_is_runtime_local_strict_and_monotonic
|
|
invoke::tests::engine_completion_notifies_after_panic_and_join_reports_failure
|
|
dns::runtime::tests::subrule_dns_rules_keep_round_robin_for_new_physical_connections
|
|
dns::runtime::tests::runtime_dns_separates_bootstrap_proxy_direct_and_hosts
|
|
dns::runtime::tests::udp_truncation_retries_the_same_upstream_over_tcp
|
|
dns::runtime::tests::concurrent_redir_host_hint_reads_and_dns_updates_complete
|
|
dns::runtime::tests::domain_policies_use_specificity_and_preserve_geosite_order
|
|
dns::runtime::tests::dns_policy_selection_is_consistent_across_snapshot_swaps
|
|
dns::runtime::tests::classical_logical_selectors_drive_dns_policy_and_sniffing_without_egress
|
|
dns::runtime::tests::rule_budget_propagates_through_fake_ip_policy_and_sniffer_without_egress
|
|
runtime::tests::prepares_builtin_routes_without_static_nodes_or_bootstrap
|
|
runtime::tests::prepares_udp_capabilities_through_runtime_wrappers
|
|
runtime::tests::prepares_shared_mux_capabilities_and_registers_only_final_graph_nodes
|
|
runtime::tests::controller_diagnostics_are_opt_in_for_business_routes
|
|
invoke::tests::null_invalid_utf8_and_oversized_input_return_json_failures
|
|
invoke::tests::same_instance_command_is_fail_fast
|
|
invoke::tests::android_protector_is_required_only_for_tun
|
|
invoke::tests::start_accepts_only_config_yaml_and_removed_prepare_is_unknown
|
|
invoke::tests::byte_dispatch_preserves_utf8_for_chinese_and_emoji_requests
|
|
)
|
|
for test_name in "${exact_tests[@]}"; do run_test 1 --lib "$test_name" -- --exact; done
|
|
run_test 1 --test quic_datagram_foundations pending_send_is_not_restarted_and_stop_cancels_without_waiting_for_writable -- --exact
|
|
- name: Feature admission and complete compilation
|
|
if: matrix.profile == 'features'
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
run_test() {
|
|
local expected_summaries="$1"
|
|
shift
|
|
local test_log
|
|
test_log="$(mktemp "$RUNNER_TEMP/vole-features.XXXXXX")"
|
|
cargo test --locked --no-default-features "$@" | tee "$test_log"
|
|
test "$(grep -Ec '^test result: ok\. [0-9]+ passed; [0-9]+ failed; [0-9]+ ignored;' "$test_log")" -eq "$expected_summaries"
|
|
test "$(grep -Ec '^test result: ok\. [1-9][0-9]* passed; 0 failed;' "$test_log")" -eq "$expected_summaries"
|
|
rm -f "$test_log"
|
|
}
|
|
protocols=(socks5 anytls shadowsocks trojan trusttunnel mieru masque vmess vless hysteria2 tuic wireguard)
|
|
cargo check --locked --no-default-features --lib
|
|
for protocol in "${protocols[@]}"; do
|
|
cargo check --locked --no-default-features --features "outbound-$protocol" --lib
|
|
run_test 1 --features "outbound-$protocol" --lib runtime::tests::integration_feature_admission_is_explicit_without_opening_sockets -- --exact
|
|
done
|
|
for feature in outbound-http inbound-http inbound-socks5 stream-transport quic-transport quinn-h3-transport quiche-h3-transport dns-quic sing-mux shadow-tls-v3 tun invoke ffi cli windows-wintun windows-uwp; do
|
|
cargo check --locked --no-default-features --features "$feature" --lib
|
|
done
|
|
dns_graph="$(mktemp "$RUNNER_TEMP/vole-dns-graph.XXXXXX")"
|
|
cargo tree --locked --no-default-features --features dns-quic --target all --edges normal,build --prefix none | tee "$dns_graph"
|
|
if grep -Eq '^(quiche|boring|boring-sys|tokio-boring) v' "$dns_graph"; then
|
|
echo "The production DNS feature must use Quinn/rustls without quiche or Boring"
|
|
exit 1
|
|
fi
|
|
rm -f "$dns_graph"
|
|
cargo check --locked --no-default-features --features cli --bin vole
|
|
run_test 2 --test feature_foundations --test hysteria2_config
|
|
cargo check --locked --lib
|
|
# Compilation only: historical listener fixtures must not execute on CI hosts.
|
|
cargo test --locked --all-features --all-targets --no-run
|
|
# The unified release matrix builds CLI and FFI with the unchanged
|
|
# production release profile, including both macOS targets.
|
|
- name: Memory-only netstack tests
|
|
if: matrix.profile == 'features'
|
|
run: cargo test --locked --manifest-path crates/vole-netstack/Cargo.toml --all-targets
|
|
|
|
release-build:
|
|
name: Build
|
|
# Reusable-workflow permissions are statically bounded by the caller, even
|
|
# for skipped jobs. Build/assemble jobs downgrade to read; only a direct
|
|
# release.yml tag push can execute the callee's write-permission publish job.
|
|
permissions:
|
|
contents: write
|
|
uses: ./.github/workflows/release.yml
|