mirror of
https://github.com/EasyTier/EasyTier.git
synced 2026-10-12 16:00:34 +00:00
ci: parallelize Linux tests and skip HAR packaging on PRs (#2678)
This commit is contained in:
@@ -3,3 +3,28 @@ slow-timeout = { period = "60s", terminate-after = 1 }
|
||||
|
||||
[profile.default.junit]
|
||||
path = "junit.xml"
|
||||
|
||||
[profile.ci]
|
||||
test-threads = 32
|
||||
|
||||
[test-groups.subnet-proxy]
|
||||
max-threads = 8
|
||||
|
||||
[[profile.default.overrides]]
|
||||
filter = 'test(tests::three_node::subnet_proxy_three_node_test)'
|
||||
test-group = 'subnet-proxy'
|
||||
|
||||
# These tests modify or inspect host-wide interfaces, routes, or BPF state.
|
||||
# They must exclude all other tests, including the isolated network fixtures.
|
||||
# Run exclusive tests first so the parallel pool does not repeatedly drain.
|
||||
[[profile.default.overrides]]
|
||||
filter = 'test(tests::upnp_test) | test(common::ifcfg::netlink::tests) | test(instance::public_ipv6_provider::platform::tests) | test(instance::dns_server::tests) | test(socket::fake_tcp::netfilter::linux_bpf::tests)'
|
||||
threads-required = 'num-test-threads'
|
||||
priority = 100
|
||||
|
||||
# These assertions require a reply within 100 ms, on the first ping, or a
|
||||
# minimum measured throughput. Competing tests can delay packet processing.
|
||||
[[profile.default.overrides]]
|
||||
filter = 'test(tests::three_node::p2p_only_test) | test(tests::three_node::relay_peer_e2e_encryption) | test(tests::three_node::whitelist_test) | test(relay_bps_limit_test) | test(instance_recv_bps_limit_test)'
|
||||
threads-required = 'num-test-threads'
|
||||
priority = 100
|
||||
|
||||
+9
-1
@@ -11,6 +11,12 @@ Test runs CI script tests, fmt, clippy, WASI, Cargo.lock validation, and
|
||||
Linux tests for `easytier`/`easytier-core`, including `three_node`. Full mode
|
||||
also checks each `easytier` feature except `macos-ne`.
|
||||
|
||||
Linux tests run on one runner with 32 concurrent nextest processes. Network
|
||||
integration tests use separate namespaces and bridges for each test. Subnet
|
||||
proxy cases run at most eight at a time. Tests that use host-wide network state,
|
||||
measure bandwidth, or make time-sensitive connectivity assertions run first,
|
||||
exclusively, before the parallel tests.
|
||||
|
||||
These four workflows skip build/test jobs for draft PRs. Path and duplicate
|
||||
content checks can skip ordinary builds and Rust jobs. The result jobs are
|
||||
`core-result`, `gui-result`, `mobile-result`, and `test`.
|
||||
@@ -51,6 +57,8 @@ It downloads their artifacts and creates a draft release.
|
||||
|
||||
## Other workflows
|
||||
|
||||
- OHOS runs independently; relevant PRs can still upload HAR artifacts.
|
||||
- OHOS runs independently. Relevant PRs run bridge tests and check the bindings
|
||||
for the host and ARM64 OHOS. Non-PR runs build and upload HAR artifacts;
|
||||
publishing and downstream builds retain their existing main/manual conditions.
|
||||
- Go, JavaScript Hosts, and Nix use their own path filters.
|
||||
- Docker is manual; it downloads a specified Core run's artifacts and pushes images.
|
||||
|
||||
+7
-1
@@ -11,6 +11,11 @@ Test 运行 CI 脚本测试、fmt、clippy、WASI、Cargo.lock 检查,以及
|
||||
`easytier`/`easytier-core` 的 Linux 测试(含 `three_node`)。全量模式还逐个
|
||||
检查 `easytier` 的 feature,排除 `macos-ne`。
|
||||
|
||||
Linux 测试使用一个 runner,nextest 总并发度为 32。网络集成测试为每个测试
|
||||
创建独立的 namespace 和网桥。子网代理用例最多同时运行八个;带宽测试、
|
||||
使用宿主机全局网络状态的测试和时间敏感的连通性测试优先独占
|
||||
执行,完成后再运行并发测试。
|
||||
|
||||
这四个 workflow 的构建和测试任务跳过 draft PR。普通构建和 Rust 任务可因
|
||||
无关路径或重复内容而跳过。汇总检查为 `core-result`、`gui-result`、
|
||||
`mobile-result` 和 `test`。
|
||||
@@ -49,6 +54,7 @@ release。
|
||||
|
||||
## 其他 workflow
|
||||
|
||||
- OHOS 独立运行,相关 PR 仍可上传 HAR。
|
||||
- OHOS 独立运行。相关 PR 运行桥接单测,并检查宿主平台和 ARM64 OHOS 的绑定。
|
||||
非 PR 构建生成并上传 HAR;发布和下游构建仍按原有主线/手动触发条件执行。
|
||||
- Go、JavaScript Hosts、Nix 使用各自的路径过滤。
|
||||
- Docker 手动触发,下载指定 Core 构建的产物并推送镜像。
|
||||
|
||||
+38
-15
@@ -80,29 +80,62 @@ jobs:
|
||||
uses: ErBWs/setup-ohos@v1
|
||||
|
||||
- name: Install ohrs
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: taiki-e/install-action@v2
|
||||
with:
|
||||
tool: ohrs
|
||||
|
||||
- name: Build HAR
|
||||
id: package
|
||||
env:
|
||||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
- name: Prepare OHOS tools
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo apt-get install -qqy \
|
||||
pkg-config curl libgl1-mesa-dev expect llvm clang lldb lld
|
||||
rustup component add rustfmt
|
||||
|
||||
sudo mkdir -p "$OHOS_NDK_HOME/native/llvm"
|
||||
sudo tee "$OHOS_NDK_HOME/native/llvm/aarch64-unknown-linux-ohos-clang.sh" >/dev/null <<'EOF'
|
||||
#!/bin/sh
|
||||
exec "$OHOS_NDK_HOME/native/llvm/bin/clang" \
|
||||
-target aarch64-linux-ohos \
|
||||
--sysroot="$OHOS_NDK_HOME/native/sysroot" \
|
||||
-D__MUSL__ "$@"
|
||||
EOF
|
||||
sudo chmod +x \
|
||||
"$OHOS_NDK_HOME/native/llvm/aarch64-unknown-linux-ohos-clang.sh"
|
||||
|
||||
- name: Check formatting
|
||||
run: |
|
||||
cargo fmt --all --manifest-path \
|
||||
easytier-contrib/easytier-ohrs/Cargo.toml -- --check
|
||||
|
||||
- name: Test OHOS bridge
|
||||
run: |
|
||||
cargo test --locked --manifest-path \
|
||||
easytier-contrib/easytier-ohrs/Cargo.toml \
|
||||
-p easytier-ohos-core -p easytier-ohos-features \
|
||||
--lib -- --test-threads=1
|
||||
|
||||
- name: Check OHOS bindings
|
||||
run: |
|
||||
cargo check --locked --manifest-path \
|
||||
easytier-contrib/easytier-ohrs/Cargo.toml \
|
||||
-p easytier-ohrs --tests
|
||||
|
||||
- name: Check OHOS target
|
||||
if: github.event_name == 'pull_request'
|
||||
working-directory: easytier-contrib/easytier-ohrs
|
||||
run: |
|
||||
source env.sh
|
||||
cargo check --locked -p easytier-ohrs --tests \
|
||||
--target aarch64-unknown-linux-ohos
|
||||
|
||||
- name: Build HAR
|
||||
if: github.event_name != 'pull_request'
|
||||
id: package
|
||||
env:
|
||||
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cargo_version=$(cargo metadata --format-version 1 --no-deps \
|
||||
--manifest-path easytier/Cargo.toml | jq -r '.packages[0].version')
|
||||
last_tag=$(git describe --tags --abbrev=0 HEAD 2>/dev/null || true)
|
||||
@@ -149,17 +182,6 @@ jobs:
|
||||
echo
|
||||
} > "$package_dir/CHANGELOG.md"
|
||||
|
||||
sudo mkdir -p "$OHOS_NDK_HOME/native/llvm"
|
||||
sudo tee "$OHOS_NDK_HOME/native/llvm/aarch64-unknown-linux-ohos-clang.sh" >/dev/null <<'EOF'
|
||||
#!/bin/sh
|
||||
exec "$OHOS_NDK_HOME/native/llvm/bin/clang" \
|
||||
-target aarch64-linux-ohos \
|
||||
--sysroot="$OHOS_NDK_HOME/native/sysroot" \
|
||||
-D__MUSL__ "$@"
|
||||
EOF
|
||||
sudo chmod +x \
|
||||
"$OHOS_NDK_HOME/native/llvm/aarch64-unknown-linux-ohos-clang.sh"
|
||||
|
||||
cd easytier-contrib/easytier-ohrs
|
||||
source env.sh
|
||||
ohrs build --release --arch aarch
|
||||
@@ -167,6 +189,7 @@ jobs:
|
||||
mv package.har "$package_name.har"
|
||||
|
||||
- name: Upload HAR
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: ${{ steps.package.outputs.name }}
|
||||
|
||||
@@ -43,6 +43,7 @@ jobs:
|
||||
env:
|
||||
RELEVANT_PATHS: >-
|
||||
["Cargo.toml", "Cargo.lock", "rust-toolchain.toml", ".cargo/**",
|
||||
".config/nextest.toml",
|
||||
"pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "easytier/**",
|
||||
"easytier-core/**", "easytier-proto/**", "easytier-rpc-build/**",
|
||||
"easytier-web/**", "easytier-gui/src-tauri/**",
|
||||
@@ -166,23 +167,11 @@ jobs:
|
||||
path: tests.tar.zst
|
||||
retention-days: 1
|
||||
|
||||
test_matrix:
|
||||
name: Test (${{ matrix.name }})
|
||||
run-tests:
|
||||
name: Run tests
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ pre_job, pre-test ]
|
||||
if: needs.pre_job.outputs.should_skip != 'true'
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- name: "easytier"
|
||||
opts: "-E 'not test(tests::three_node)' --test-threads 1 --no-fail-fast"
|
||||
|
||||
- name: "three_node"
|
||||
opts: "-E 'test(tests::three_node) and not test(subnet_proxy_three_node_test)' --test-threads 1 --no-fail-fast"
|
||||
|
||||
- name: "three_node::subnet_proxy_three_node_test"
|
||||
opts: "-E 'test(subnet_proxy_three_node_test)' --test-threads 1 --no-fail-fast"
|
||||
steps:
|
||||
- uses: actions/checkout@v5
|
||||
|
||||
@@ -217,11 +206,11 @@ jobs:
|
||||
run: |
|
||||
sudo prlimit --pid $$ --nofile=1048576:1048576
|
||||
sudo -E env "PATH=$PATH" EASYTIER_LINUX_BPF_INTEGRATION=required \
|
||||
cargo nextest run --archive-file tests.tar.zst ${{ matrix.opts }}
|
||||
cargo nextest run --archive-file tests.tar.zst --profile ci --no-fail-fast
|
||||
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ pre_job, ci-scripts, check, pre-test, test_matrix ]
|
||||
needs: [ pre_job, ci-scripts, check, pre-test, run-tests ]
|
||||
if: ${{ always() }}
|
||||
steps:
|
||||
- name: Mark result as failed
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+46
-51
@@ -1,6 +1,9 @@
|
||||
#[cfg(target_os = "linux")]
|
||||
mod three_node;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
mod network;
|
||||
|
||||
mod ipv6_test;
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
@@ -68,21 +71,35 @@ pub fn del_netns(name: &str) {
|
||||
.output();
|
||||
}
|
||||
|
||||
fn run_network_command(program: &str, args: &[&str]) {
|
||||
let output = std::process::Command::new(program)
|
||||
.args(args)
|
||||
.output()
|
||||
.unwrap();
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"{} {:?} failed ({}): stdout={}, stderr={}",
|
||||
program,
|
||||
args,
|
||||
output.status,
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
}
|
||||
|
||||
pub fn create_netns(name: &str, ipv4: &str, ipv6: &str) {
|
||||
// create netns
|
||||
let _ = std::process::Command::new("ip")
|
||||
.args(["netns", "add", name])
|
||||
.output()
|
||||
.unwrap();
|
||||
run_network_command("ip", &["netns", "add", name]);
|
||||
|
||||
// set lo up
|
||||
let _ = std::process::Command::new("ip")
|
||||
.args(["netns", "exec", name, "ip", "link", "set", "lo", "up"])
|
||||
.output()
|
||||
.unwrap();
|
||||
run_network_command(
|
||||
"ip",
|
||||
&["netns", "exec", name, "ip", "link", "set", "lo", "up"],
|
||||
);
|
||||
|
||||
let _ = std::process::Command::new("ip")
|
||||
.args([
|
||||
run_network_command(
|
||||
"ip",
|
||||
&[
|
||||
"link",
|
||||
"add",
|
||||
get_host_veth_name(name),
|
||||
@@ -91,17 +108,17 @@ pub fn create_netns(name: &str, ipv4: &str, ipv6: &str) {
|
||||
"peer",
|
||||
"name",
|
||||
get_guest_veth_name(name),
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
],
|
||||
);
|
||||
|
||||
let _ = std::process::Command::new("ip")
|
||||
.args(["link", "set", get_guest_veth_name(name), "netns", name])
|
||||
.output()
|
||||
.unwrap();
|
||||
run_network_command(
|
||||
"ip",
|
||||
&["link", "set", get_guest_veth_name(name), "netns", name],
|
||||
);
|
||||
|
||||
let _ = std::process::Command::new("ip")
|
||||
.args([
|
||||
run_network_command(
|
||||
"ip",
|
||||
&[
|
||||
"netns",
|
||||
"exec",
|
||||
name,
|
||||
@@ -110,18 +127,15 @@ pub fn create_netns(name: &str, ipv4: &str, ipv6: &str) {
|
||||
"set",
|
||||
get_guest_veth_name(name),
|
||||
"up",
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
],
|
||||
);
|
||||
|
||||
let _ = std::process::Command::new("ip")
|
||||
.args(["link", "set", get_host_veth_name(name), "up"])
|
||||
.output()
|
||||
.unwrap();
|
||||
run_network_command("ip", &["link", "set", get_host_veth_name(name), "up"]);
|
||||
|
||||
for ip in [ipv4, ipv6] {
|
||||
let _ = std::process::Command::new("ip")
|
||||
.args([
|
||||
run_network_command(
|
||||
"ip",
|
||||
&[
|
||||
"netns",
|
||||
"exec",
|
||||
name,
|
||||
@@ -131,36 +145,17 @@ pub fn create_netns(name: &str, ipv4: &str, ipv6: &str) {
|
||||
ip,
|
||||
"dev",
|
||||
get_guest_veth_name(name),
|
||||
])
|
||||
.output()
|
||||
.unwrap();
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepare_bridge(name: &str) {
|
||||
// del bridge with brctl
|
||||
let _ = std::process::Command::new("brctl")
|
||||
.args(["delbr", name])
|
||||
.output();
|
||||
|
||||
// create new br
|
||||
let _ = std::process::Command::new("brctl")
|
||||
.args(["addbr", name])
|
||||
.output();
|
||||
}
|
||||
|
||||
pub fn add_ns_to_bridge(br_name: &str, ns_name: &str) {
|
||||
// use brctl to add ns to bridge
|
||||
let _ = std::process::Command::new("brctl")
|
||||
.args(["addif", br_name, get_host_veth_name(ns_name)])
|
||||
.output()
|
||||
.unwrap();
|
||||
run_network_command("brctl", &["addif", br_name, get_host_veth_name(ns_name)]);
|
||||
|
||||
// set bridge up
|
||||
let _ = std::process::Command::new("ip")
|
||||
.args(["link", "set", br_name, "up"])
|
||||
.output()
|
||||
.unwrap();
|
||||
run_network_command("ip", &["link", "set", br_name, "up"]);
|
||||
}
|
||||
|
||||
fn check_route(
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
use crate::common::netns::{NetNS, ROOT_NETNS_NAME};
|
||||
|
||||
use super::{add_ns_to_bridge, create_netns, del_netns, run_network_command};
|
||||
|
||||
/// Owns one test's host links and namespaces. Keep this alive until its
|
||||
/// instances and background tasks have stopped, including on setup failures.
|
||||
pub(super) struct TestNetwork {
|
||||
directory: tempfile::TempDir,
|
||||
namespaces: Vec<(&'static str, String)>,
|
||||
links: Vec<(&'static str, String)>,
|
||||
}
|
||||
|
||||
impl TestNetwork {
|
||||
pub(super) fn new() -> Self {
|
||||
Self {
|
||||
directory: tempfile::Builder::new()
|
||||
.prefix("et")
|
||||
.rand_bytes(4)
|
||||
.tempdir()
|
||||
.unwrap(),
|
||||
namespaces: Vec::new(),
|
||||
links: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn prefix(&self) -> &str {
|
||||
self.directory.path().file_name().unwrap().to_str().unwrap()
|
||||
}
|
||||
|
||||
pub(super) fn namespace(&self, name: &str) -> &str {
|
||||
&self
|
||||
.namespaces
|
||||
.iter()
|
||||
.find(|(key, _)| *key == name)
|
||||
.unwrap()
|
||||
.1
|
||||
}
|
||||
|
||||
pub(super) fn link(&self, name: &str) -> &str {
|
||||
&self.links.iter().find(|(key, _)| *key == name).unwrap().1
|
||||
}
|
||||
|
||||
fn add_namespace(&mut self, name: &'static str) {
|
||||
// veth_<namespace>_h must fit Linux's 15-byte interface name limit.
|
||||
let actual = format!("{}{:02x}", self.prefix(), self.namespaces.len());
|
||||
assert!(actual.len() <= 8);
|
||||
assert!(!self.namespaces.iter().any(|(key, _)| *key == name));
|
||||
self.namespaces.push((name, actual));
|
||||
}
|
||||
|
||||
pub(super) fn create_namespace(&mut self, name: &'static str, ipv4: &str, ipv6: &str) {
|
||||
self.add_namespace(name);
|
||||
create_netns(self.namespace(name), ipv4, ipv6);
|
||||
}
|
||||
|
||||
pub(super) fn create_empty_namespace(&mut self, name: &'static str) {
|
||||
self.add_namespace(name);
|
||||
let actual = self.namespace(name);
|
||||
run_network_command("ip", &["netns", "add", actual]);
|
||||
run_network_command(
|
||||
"ip",
|
||||
&["netns", "exec", actual, "ip", "link", "set", "lo", "up"],
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn add_link(&mut self, name: &'static str) {
|
||||
let actual = format!("{}l{:02x}", self.prefix(), self.links.len());
|
||||
assert!(!self.links.iter().any(|(key, _)| *key == name));
|
||||
self.links.push((name, actual));
|
||||
}
|
||||
|
||||
pub(super) fn create_bridge(&mut self, name: &'static str) {
|
||||
self.add_link(name);
|
||||
run_network_command("brctl", &["addbr", self.link(name)]);
|
||||
run_network_command("ip", &["link", "set", self.link(name), "up"]);
|
||||
}
|
||||
|
||||
pub(super) fn add_to_bridge(&self, bridge: &str, namespace: &str) {
|
||||
add_ns_to_bridge(self.link(bridge), self.namespace(namespace));
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for TestNetwork {
|
||||
fn drop(&mut self) {
|
||||
let _guard = NetNS::new(Some(ROOT_NETNS_NAME.to_owned())).guard();
|
||||
for (_, namespace) in &self.namespaces {
|
||||
del_netns(namespace);
|
||||
}
|
||||
for (_, link) in self.links.iter().rev() {
|
||||
let _ = std::process::Command::new("ip")
|
||||
.args(["link", "del", link])
|
||||
.output();
|
||||
}
|
||||
}
|
||||
}
|
||||
+473
-312
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user