From dc04477eb5e60fd9c9c5b72aebd768aa62bda861 Mon Sep 17 00:00:00 2001 From: Eason0729 <30045503+Eason0729@users.noreply.github.com> Date: Sun, 28 Jan 2024 18:09:19 +0800 Subject: [PATCH] Fast forward master to issue23 (#25) * feat(Testsuit): :sparkles: make testsuit an cli application instead of a collection of rstest * fix(Testsuit): :bug: bring nighty frontend * feat(Testsuit): :white_check_mark: add create and add_to test * test(Testsuit): :test_tube: lua RE: wrong test input * fix(Judger): :bug: change return code form 1 to 0 * feat(Backend): :sparkles: auto generate config and database folder * cargo clippy --- Cargo.lock | 151 ++++++++++-------- Cargo.toml | 2 + backend/Cargo.toml | 19 ++- backend/readme.md | 20 +++ backend/src/endpoint/submit.rs | 8 +- backend/src/entity/mod.rs | 3 +- backend/src/init/config.rs | 28 ++-- backend/src/init/db.rs | 28 ++-- .../{test/data/problem.rs => init/error.rs} | 0 backend/src/init/logger.rs | 96 ++++++----- backend/src/init/mod.rs | 1 + backend/src/main.rs | 2 - backend/src/server.rs | 30 +++- backend/src/test/data/mod.rs | 26 --- backend/src/test/data/paginator.rs | 42 ----- backend/src/test/mod.rs | 1 - judger/justfile | 1 - judger/plugins/c-11/compile.c | 3 +- judger/plugins/cpp-11/compile.c | 3 +- judger/plugins/rlua-54/src/main.rs | 6 +- judger/src/init/config.rs | 3 + judger/src/init/mod.rs | 2 + judger/src/init/volumn.rs | 11 ++ judger/src/langs/artifact.rs | 2 +- judger/src/main.rs | 3 +- judger/src/{test => tests}/grpc.rs | 0 judger/src/{test => tests}/langs.rs | 4 + judger/src/{test => tests}/mod.rs | 3 - judger/src/{test => tests}/sandbox.rs | 0 proto/backend.proto | 11 +- ...rust-toolchain.toml => rust-toolchain.toml | 0 testsuit/.gitignore | 3 +- testsuit/Cargo.toml | 12 +- testsuit/data.toml | 8 + testsuit/src/checks.rs | 33 ++++ testsuit/src/client.rs | 6 +- testsuit/src/{constant.rs => constants.rs} | 1 + testsuit/src/empty/mod.rs | 7 - testsuit/src/list/mod.rs | 0 testsuit/src/macro_tool.rs | 15 ++ testsuit/src/main.rs | 78 ++++++++- testsuit/src/prepare/mod.rs | 7 - testsuit/src/tests/add_to/mod.rs | 25 +++ testsuit/src/tests/add_to/problem.rs | 30 ++++ testsuit/src/tests/create/contest.rs | 42 +++++ testsuit/src/tests/create/mod.rs | 40 +++++ .../src/{prepare => tests/create}/problem.rs | 39 ++--- testsuit/src/tests/create/testcase.rs | 44 +++++ testsuit/src/tests/create/user.rs | 42 +++++ testsuit/src/{ => tests}/empty/login.rs | 29 ++-- testsuit/src/tests/empty/mod.rs | 35 ++++ testsuit/src/{ => tests}/empty/problem.rs | 26 +-- testsuit/src/tests/mod.rs | 87 ++++++++++ testsuit/src/tests/operate/admin.rs | 50 ++++++ testsuit/src/tests/operate/mod.rs | 21 +++ testsuit/src/tests/ui.rs | 41 +++++ 56 files changed, 907 insertions(+), 323 deletions(-) create mode 100644 backend/readme.md rename backend/src/{test/data/problem.rs => init/error.rs} (100%) delete mode 100644 backend/src/test/data/mod.rs delete mode 100644 backend/src/test/data/paginator.rs delete mode 100644 backend/src/test/mod.rs create mode 100644 judger/src/init/volumn.rs rename judger/src/{test => tests}/grpc.rs (100%) rename judger/src/{test => tests}/langs.rs (92%) rename judger/src/{test => tests}/mod.rs (54%) rename judger/src/{test => tests}/sandbox.rs (100%) rename frontend/rust-toolchain.toml => rust-toolchain.toml (100%) create mode 100644 testsuit/data.toml create mode 100644 testsuit/src/checks.rs rename testsuit/src/{constant.rs => constants.rs} (74%) delete mode 100644 testsuit/src/empty/mod.rs delete mode 100644 testsuit/src/list/mod.rs create mode 100644 testsuit/src/macro_tool.rs delete mode 100644 testsuit/src/prepare/mod.rs create mode 100644 testsuit/src/tests/add_to/mod.rs create mode 100644 testsuit/src/tests/add_to/problem.rs create mode 100644 testsuit/src/tests/create/contest.rs create mode 100644 testsuit/src/tests/create/mod.rs rename testsuit/src/{prepare => tests/create}/problem.rs (58%) create mode 100644 testsuit/src/tests/create/testcase.rs create mode 100644 testsuit/src/tests/create/user.rs rename testsuit/src/{ => tests}/empty/login.rs (54%) create mode 100644 testsuit/src/tests/empty/mod.rs rename testsuit/src/{ => tests}/empty/problem.rs (69%) create mode 100644 testsuit/src/tests/mod.rs create mode 100644 testsuit/src/tests/operate/admin.rs create mode 100644 testsuit/src/tests/operate/mod.rs create mode 100644 testsuit/src/tests/ui.rs diff --git a/Cargo.lock b/Cargo.lock index 25457153..656daafa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -296,9 +296,9 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.5" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d664a92ecae85fd0a7392615844904654d1d5f5514837f471ddef4a057aba1b6" +checksum = "6e2e1ebcb11de5c03c67de28a7df593d32191b44939c482e97702baaaa6ab6a5" dependencies = [ "anstyle", "anstyle-parse", @@ -931,24 +931,6 @@ dependencies = [ "thiserror", ] -[[package]] -name = "cached" -version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69b0116662497bc24e4b177c90eaf8870e39e2714c3fcfa296327a93f593fc21" -dependencies = [ - "ahash 0.8.6", - "async-trait", - "cached_proc_macro", - "cached_proc_macro_types", - "futures", - "hashbrown 0.14.3", - "instant", - "once_cell", - "thiserror", - "tokio", -] - [[package]] name = "cached_proc_macro" version = "0.18.1" @@ -1052,9 +1034,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.4.11" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfaff671f6b22ca62406885ece523383b9b64022e341e53e009a62ebc47a45f2" +checksum = "1e578d6ec4194633722ccf9544794b71b1385c3c027efe0c55db226fc880865c" dependencies = [ "clap_builder", "clap_derive", @@ -1062,9 +1044,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.4.11" +version = "4.4.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a216b506622bb1d316cd51328dce24e07bdff4a6128a47c7e7fad11878d5adbb" +checksum = "4df4df40ec50c46000231c914968278b1eb05098cf8f1b3a518a95030e71d1c7" dependencies = [ "anstream", "anstyle", @@ -1125,6 +1107,19 @@ dependencies = [ "toml 0.5.11", ] +[[package]] +name = "console" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys 0.52.0", +] + [[package]] name = "console_error_panic_hook" version = "0.1.7" @@ -1502,6 +1497,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + [[package]] name = "encoding_rs" version = "0.8.33" @@ -1815,12 +1816,6 @@ version = "0.3.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" -[[package]] -name = "futures-timer" -version = "3.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" - [[package]] name = "futures-util" version = "0.3.29" @@ -2278,6 +2273,29 @@ dependencies = [ "hashbrown 0.14.3", ] +[[package]] +name = "indicatif" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "unicode-width", +] + +[[package]] +name = "indicatif-log-bridge" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2963046f28a204e3e3fd7e754fd90a6235da05b5378f24707ff0ec9513725ce3" +dependencies = [ + "indicatif", + "log", +] + [[package]] name = "inherent" version = "1.0.10" @@ -2671,7 +2689,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b31087173c60e25c329a1c6786756dd9ee97750b378622df4d780db160a09040" dependencies = [ - "cached 0.45.1", + "cached", "cfg-if", "gloo-net", "itertools 0.12.0", @@ -3069,6 +3087,12 @@ dependencies = [ "libc", ] +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + [[package]] name = "object" version = "0.32.1" @@ -3441,6 +3465,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + [[package]] name = "powerfmt" version = "0.2.0" @@ -3453,6 +3483,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "pretty_env_logger" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" +dependencies = [ + "env_logger", + "log", +] + [[package]] name = "prettyplease" version = "0.1.25" @@ -3815,12 +3855,6 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f" -[[package]] -name = "relative-path" -version = "1.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e898588f33fdd5b9420719948f9f2a32c922a246964576f71ba7f24f80610fbc" - [[package]] name = "rend" version = "0.4.1" @@ -3953,35 +3987,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "rstest" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97eeab2f3c0a199bc4be135c36c924b6590b88c377d416494288c14f2db30199" -dependencies = [ - "futures", - "futures-timer", - "rstest_macros", - "rustc_version", -] - -[[package]] -name = "rstest_macros" -version = "0.18.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d428f8247852f894ee1be110b375111b586d4fa431f6c46e64ba5a0dcccbe605" -dependencies = [ - "cfg-if", - "glob", - "proc-macro2", - "quote", - "regex", - "relative-path", - "rustc_version", - "syn 2.0.48", - "unicode-ident", -] - [[package]] name = "rstml" version = "0.11.2" @@ -5004,17 +5009,21 @@ version = "0.1.0" dependencies = [ "async-std", "base64", - "cached 0.47.0", "chrono", + "clap", "futures", "futures-core", "http-body", "hyper", + "indicatif", + "indicatif-log-bridge", "log", + "pretty_env_logger", "prost 0.12.3", "prost-types 0.12.3", - "rstest", + "serde", "thiserror", + "toml 0.7.8", "tonic 0.10.2", "tonic-build 0.10.2", "tonic-web", @@ -5585,6 +5594,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + [[package]] name = "unicode-xid" version = "0.2.4" diff --git a/Cargo.toml b/Cargo.toml index 93f470b1..e8d0d2f0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ prost = "0.12.3" prost-types = "0.12.3" tonic-build = "0.10.2" tonic-web = "0.10.2" +serde = "1.0.163" +toml = "0.7.4" [workspace.dependencies.tonic] version = "0.10.2" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index a7b023a2..d931928f 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -10,7 +10,7 @@ tikv-jemallocator = "0.5" log = "0.4.18" paste = "1.0.12" prost = { workspace = true } -toml = "0.7.4" +toml = { workspace = true } prost-types = { workspace = true } # entity = { path = "./entity" } chrono = "0.4.26" @@ -38,6 +38,8 @@ opentelemetry_sdk = { version = "0.21.1", features = ["rt-tokio", "metrics"] } opentelemetry-stdout = { version = "0.2.0", features = ["metrics"] } opentelemetry-semantic-conventions = "0.13.0" opentelemetry-otlp = { version = "0.14.0", features = ["metrics"] } +migration = { path = "./migration" } +sea-orm-cli = "0.12.12" [dependencies.reqwest] version = "0.11.22" @@ -76,7 +78,7 @@ features = [ ] [dependencies.serde] -version = "1.0.163" +workspace = true features = ["derive"] [dependencies.tonic] @@ -87,19 +89,16 @@ features = ["transport", "channel", "codegen", "prost","tls"] version = "0.9.8" features = ["mutex", "spin_mutex", "rwlock"] -[dev-dependencies] -migration = { path = "./migration" } -sea-orm-cli = "0.12.12" - -[dev-dependencies.sea-orm-migration] +[dependencies.sea-orm-migration] workspace = true features = [ - # Enable at least one `ASYNC_RUNTIME` and `DATABASE_DRIVER` feature if you want to run migration via CLI. - # View the list of supported features at https://www.sea-ql.org/SeaORM/docs/install-and-config/database-and-async-runtime. - # e.g. "runtime-tokio-native-tls", "sqlx-sqlite","with-chrono" ] [build-dependencies] tonic-build = { workspace = true } + +[features] +default = [] +standalone = [] \ No newline at end of file diff --git a/backend/readme.md b/backend/readme.md new file mode 100644 index 00000000..b4d2a718 --- /dev/null +++ b/backend/readme.md @@ -0,0 +1,20 @@ +```toml +bind_address = "0.0.0.0:8081" +log_level = 0 +opentelemetry = false + +[database] +path = "database/backend.sqlite" +salt = "be sure to change it" + +[[judger]] +name = "http://127.0.0.1:8080" +type = "static" + +[grpc] + +[imgur] +client_id = "fffffffffffffff" +client_secret = "ffffffffffffffffffffffffffffffffffffffff" + +``` \ No newline at end of file diff --git a/backend/src/endpoint/submit.rs b/backend/src/endpoint/submit.rs index d7778aa2..199a9687 100644 --- a/backend/src/endpoint/submit.rs +++ b/backend/src/endpoint/submit.rs @@ -145,13 +145,13 @@ impl SubmitSet for Arc { let (auth, req) = self.parse_request(req).await?; let (user_id, _) = auth.ok_or_default()?; - if req.info.code.len() > SUBMIT_CODE_LEN { + if req.code.len() > SUBMIT_CODE_LEN { return Err(Error::BufferTooLarge("info.code").into()); } - let lang = Uuid::parse_str(req.info.lang.as_str()).map_err(Into::::into)?; + let lang = Uuid::parse_str(req.lang.as_str()).map_err(Into::::into)?; - let problem = problem::Entity::find_by_id(req.info.problem_id) + let problem = problem::Entity::find_by_id(req.problem_id) .one(db) .await .map_err(Into::::into)? @@ -172,7 +172,7 @@ impl SubmitSet for Arc { } let submit = SubmitBuilder::default() - .code(req.info.code) + .code(req.code) .lang(lang) .time_limit(problem.time) .memory_limit(problem.memory) diff --git a/backend/src/entity/mod.rs b/backend/src/entity/mod.rs index 709708c8..55db5039 100644 --- a/backend/src/entity/mod.rs +++ b/backend/src/entity/mod.rs @@ -1,6 +1,5 @@ use sea_orm::{ - entity::prelude::*, - EntityTrait, FromQueryResult, PrimaryKeyTrait, QueryFilter, Select, + entity::prelude::*, EntityTrait, FromQueryResult, PrimaryKeyTrait, QueryFilter, Select, }; use sea_orm::ColumnTrait; diff --git a/backend/src/init/config.rs b/backend/src/init/config.rs index 97a26ec3..375b936d 100644 --- a/backend/src/init/config.rs +++ b/backend/src/init/config.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::{path::PathBuf, str::FromStr}; use ip_network::IpNetwork; use serde::{Deserialize, Serialize}; @@ -19,12 +19,14 @@ pub struct GlobalConfig { pub judger: Vec, #[serde(default)] pub grpc: GrpcOption, - #[serde(default)] + #[serde(default = "default_opentelemetry")] pub opentelemetry: Option, #[serde(default)] pub imgur: Imgur, - #[serde(default)] - pub trust_host: Vec, +} + +fn default_opentelemetry() -> Option { + Some(true) } fn default_bind_address() -> String { @@ -42,6 +44,9 @@ fn default_judger() -> Vec { pub struct Database { pub path: String, pub salt: String, + #[cfg(feature = "standalone")] + #[serde(default)] + pub migrate: Option, } impl Default for Database { @@ -49,6 +54,8 @@ impl Default for Database { Self { path: "database/backend.sqlite".to_owned(), salt: "be sure to change it".to_owned(), + #[cfg(feature = "standalone")] + migrate: Some(true), } } } @@ -76,17 +83,18 @@ impl Default for JudgerType { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct GrpcOption { - pub trust_x_forwarded_for: bool, - pub public_pem: PathBuf, - pub private_pem: PathBuf, + pub public_pem: Option, + pub private_pem: Option, + #[serde(default)] + pub trust_host: Vec, } impl Default for GrpcOption { fn default() -> Self { Self { - trust_x_forwarded_for: false, - public_pem: "cert.pem".into(), - private_pem: "key.pem".into(), + public_pem: Some("cert.pem".into()), + private_pem: Some("key.pem".into()), + trust_host: vec![IpNetwork::from_str("255.255.255.255/32").unwrap()], } } } diff --git a/backend/src/init/db.rs b/backend/src/init/db.rs index cdfcc411..2a4268c4 100644 --- a/backend/src/init/db.rs +++ b/backend/src/init/db.rs @@ -13,7 +13,6 @@ pub static DB: OnceCell = OnceCell::const_new(); #[instrument(skip_all, name = "construct_db",parent=span)] pub async fn init(config: &config::Database, crypto: &CryptoController, span: &Span) { - // sqlite://database/backend.sqlite?mode=rwc let uri = format!("sqlite://{}?mode=rwc&cache=private", config.path.clone()); let db = Database::connect(&uri) @@ -28,21 +27,30 @@ pub async fn init(config: &config::Database, crypto: &CryptoController, span: &S .await .unwrap(); + #[cfg(feature = "standalone")] + if config.migrate == Some(true) { + migrate(&db).await; + } + init_user(&db, crypto).await; DB.set(db).ok(); } -// fn hash(config: &config::Database, src: &str) -> Vec { -// digest::digest( -// &digest::SHA256, -// &[src.as_bytes(), config.salt.as_bytes()].concat(), -// ) -// .as_ref() -// .to_vec() -// } + +#[cfg(feature = "standalone")] +async fn migrate(db: &DatabaseConnection) { + run_migrate( + ::migration::Migrator, + db, + Some(MigrateSubcommands::Up { num: None }), + false, + ) + .await + .expect("Unable to setup database migration"); +} #[instrument(skip_all, name = "construct_admin")] -pub async fn init_user(db: &DatabaseConnection, crypto: &CryptoController) { +async fn init_user(db: &DatabaseConnection, crypto: &CryptoController) { if crate::entity::user::Entity::find().count(db).await.unwrap() != 0 { return; } diff --git a/backend/src/test/data/problem.rs b/backend/src/init/error.rs similarity index 100% rename from backend/src/test/data/problem.rs rename to backend/src/init/error.rs diff --git a/backend/src/init/logger.rs b/backend/src/init/logger.rs index 2cf2e41e..9b479200 100644 --- a/backend/src/init/logger.rs +++ b/backend/src/init/logger.rs @@ -1,5 +1,6 @@ use opentelemetry::global; use opentelemetry::KeyValue; +use opentelemetry_sdk::metrics::reader::MetricReader; use opentelemetry_sdk::Resource; use opentelemetry_sdk::{ metrics::{ @@ -35,6 +36,19 @@ fn resource() -> Resource { ) } +// Construct MeterProvider for MetricsLayer +fn init_meter_provider(reader: impl MetricReader) -> MeterProvider { + let meter_provider = MeterProvider::builder() + .with_resource(resource()) + .with_reader(reader) + .build(); + + global::set_meter_provider(meter_provider.clone()); + + meter_provider +} + +// Construct Tracer for OpenTelemetryLayer fn init_tracer() -> Tracer { opentelemetry_otlp::new_pipeline() .tracing() @@ -54,39 +68,43 @@ fn init_tracer() -> Tracer { .unwrap() } -fn init_meter_provider(opentelemetry: bool) -> MeterProvider { - // For debugging in development - let stdout_reader = PeriodicReader::builder( - opentelemetry_stdout::MetricsExporter::default(), - runtime::Tokio, - ) - .build(); - - let mut meter_provider = MeterProvider::builder() - .with_resource(resource()) - .with_reader(stdout_reader); - - if opentelemetry { - let exporter = opentelemetry_otlp::new_exporter() - .tonic() - .build_metrics_exporter( - Box::new(DefaultAggregationSelector::new()), - Box::new(DefaultTemporalitySelector::new()), - ) - .unwrap(); - - let otlp_reader = PeriodicReader::builder(exporter, runtime::Tokio) - .with_interval(std::time::Duration::from_secs(30)) - .build(); - - meter_provider = meter_provider.with_reader(otlp_reader); - } - - let meter_provider = meter_provider.build(); - - global::set_meter_provider(meter_provider.clone()); +// Initialize tracing-subscriber and return OtelGuard for opentelemetry-related termination processing +fn init_tracing_subscriber(level: Level, opentelemetry: bool) -> OtelGuard { + let meter_provider = init_meter_provider(match opentelemetry { + true => { + let exporter = opentelemetry_otlp::new_exporter() + .tonic() + .build_metrics_exporter( + Box::new(DefaultAggregationSelector::new()), + Box::new(DefaultTemporalitySelector::new()), + ) + .unwrap(); + PeriodicReader::builder(exporter, runtime::Tokio) + .with_interval(std::time::Duration::from_secs(30)) + .build() + } + false => PeriodicReader::builder( + opentelemetry_stdout::MetricsExporter::default(), + runtime::Tokio, + ) + .build(), + }); + + match opentelemetry { + true => tracing_subscriber::registry() + .with(tracing_subscriber::filter::LevelFilter::from_level(level)) + .with(tracing_subscriber::fmt::layer()) + .with(MetricsLayer::new(meter_provider.clone())) + .with(OpenTelemetryLayer::new(init_tracer())) + .init(), + false => tracing_subscriber::registry() + .with(tracing_subscriber::filter::LevelFilter::from_level(level)) + .with(tracing_subscriber::fmt::layer()) + .with(MetricsLayer::new(meter_provider.clone())) + .init(), + }; - meter_provider + OtelGuard { meter_provider } } pub struct OtelGuard { @@ -101,16 +119,8 @@ impl Drop for OtelGuard { opentelemetry::global::shutdown_tracer_provider(); } } -fn init_tracing_subscriber(level: Level, opentelemetry: bool) -> OtelGuard { - let meter_provider = init_meter_provider(opentelemetry); - - tracing_subscriber::registry() - .with(tracing_subscriber::filter::LevelFilter::from_level(level)) - .with(tracing_subscriber::fmt::layer().with_thread_ids(false)) - .with(MetricsLayer::new(meter_provider.clone())) - .with(OpenTelemetryLayer::new(init_tracer())) - .init(); +fn init_panic_hook() { std::panic::set_hook(Box::new(|panic| { if let Some(location) = panic.location() { tracing::error!( @@ -123,11 +133,11 @@ fn init_tracing_subscriber(level: Level, opentelemetry: bool) -> OtelGuard { tracing::error!(message = %panic); } })); - - OtelGuard { meter_provider } } pub fn init(config: &GlobalConfig) -> OtelGuard { + init_panic_hook(); + let level = match config.log_level { 0 => Level::TRACE, 1 => Level::DEBUG, diff --git a/backend/src/init/mod.rs b/backend/src/init/mod.rs index 943919b9..e5451d70 100644 --- a/backend/src/init/mod.rs +++ b/backend/src/init/mod.rs @@ -1,5 +1,6 @@ pub mod config; pub mod db; +pub mod error; pub mod logger; // pub async fn new() -> (GlobalConfig, OtelGuard) { diff --git a/backend/src/main.rs b/backend/src/main.rs index f704923f..472c22ad 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -7,8 +7,6 @@ pub mod grpc; pub mod init; pub mod macro_tool; pub mod server; -#[cfg(test)] -pub mod test; pub mod util; #[global_allocator] diff --git a/backend/src/server.rs b/backend/src/server.rs index a0e407d4..57bf8210 100644 --- a/backend/src/server.rs +++ b/backend/src/server.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use tonic::transport; +use tonic::transport::{self, Identity, ServerTlsConfig}; use tracing::{span, Instrument, Level}; use crate::{ @@ -57,14 +57,36 @@ impl Server { crypto, metrics: metrics::MetricsController::new(&otel_guard.meter_provider), imgur: imgur::ImgurController::new(&config.imgur), - rate_limit: rate_limit::RateLimitController::new(&config.trust_host), + rate_limit: rate_limit::RateLimitController::new(&config.grpc.trust_host), config, _otel_guard: otel_guard, }) } pub async fn start(self: Arc) { - transport::Server::builder() - .accept_http1(true) + let mut identity = None; + + if self.config.grpc.private_pem.is_some() { + let private_pem = self.config.grpc.private_pem.as_ref().unwrap(); + let public_pem = self + .config + .grpc + .public_pem + .as_ref() + .expect("public pem should set if private pem is set"); + + let cert = std::fs::read_to_string(public_pem).expect("cannot read public pem"); + let key = std::fs::read_to_string(private_pem).expect("cannot read private pem"); + + identity = Some(Identity::from_pem(cert, key)); + } + + let server = match identity { + Some(identity) => transport::Server::builder() + .tls_config(ServerTlsConfig::new().identity(identity)) + .unwrap(), + None => transport::Server::builder().accept_http1(true), + }; + server .max_frame_size(Some(MAX_FRAME_SIZE)) .add_service(tonic_web::enable(ProblemSetServer::new(self.clone()))) .add_service(tonic_web::enable(EducationSetServer::new(self.clone()))) diff --git a/backend/src/test/data/mod.rs b/backend/src/test/data/mod.rs deleted file mode 100644 index bd493ce2..00000000 --- a/backend/src/test/data/mod.rs +++ /dev/null @@ -1,26 +0,0 @@ -use sea_orm::{Database, DatabaseConnection}; -use sea_orm_cli::cli::MigrateSubcommands; -use tonic::async_trait; - -pub mod paginator; -pub mod problem; - -#[async_trait] -pub trait Data { - async fn insert(db: &DatabaseConnection); - async fn connect() -> DatabaseConnection { - let db = Database::connect("sqlite::memory:").await.unwrap(); - sea_orm_migration::cli::run_migrate( - migration::Migrator, - &db, - Some(MigrateSubcommands::Init), - false, - ) - .await - .unwrap(); - - Self::insert(&db).await; - - db - } -} diff --git a/backend/src/test/data/paginator.rs b/backend/src/test/data/paginator.rs deleted file mode 100644 index 2c30ef65..00000000 --- a/backend/src/test/data/paginator.rs +++ /dev/null @@ -1,42 +0,0 @@ -use crate::entity::*; -use sea_orm::{prelude::*, IntoActiveModel}; -use tonic::async_trait; - -pub struct PaginatorData; - -#[async_trait] -impl super::Data for PaginatorData { - async fn insert(db: &DatabaseConnection) { - user::Model { - id: 1, - permission: 4, - score: 0, - username: todo!(), - password: todo!(), - create_at: todo!(), - } - .into_active_model() - .save(db) - .await - .unwrap(); - problem::Model { - id: 1, - user_id: 2, - contest_id: todo!(), - accept_count: todo!(), - submit_count: todo!(), - ac_rate: todo!(), - memory: todo!(), - time: todo!(), - difficulty: todo!(), - public: todo!(), - tags: todo!(), - title: todo!(), - content: todo!(), - create_at: todo!(), - update_at: todo!(), - match_rule: todo!(), - order: todo!(), - }; - } -} diff --git a/backend/src/test/mod.rs b/backend/src/test/mod.rs deleted file mode 100644 index 7a345e4c..00000000 --- a/backend/src/test/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod data; diff --git a/judger/justfile b/judger/justfile index f2c3c941..ef0b113c 100644 --- a/judger/justfile +++ b/judger/justfile @@ -33,7 +33,6 @@ test: run: sudo rm -rf .temp/* - mkdir -p .temp cargo run ci-test: diff --git a/judger/plugins/c-11/compile.c b/judger/plugins/c-11/compile.c index cae07cda..43f2085d 100644 --- a/judger/plugins/c-11/compile.c +++ b/judger/plugins/c-11/compile.c @@ -9,6 +9,7 @@ if (e == x) \ { \ printf("4: %m\n", errno); \ + fflush(stdout); \ return 1; \ } \ } @@ -39,5 +40,5 @@ int main() handle(wait(NULL),-1); printf("0: success!\n"); - return 1; + return 0; } \ No newline at end of file diff --git a/judger/plugins/cpp-11/compile.c b/judger/plugins/cpp-11/compile.c index cad6d2cc..dabe1dfe 100644 --- a/judger/plugins/cpp-11/compile.c +++ b/judger/plugins/cpp-11/compile.c @@ -9,6 +9,7 @@ if (e == x) \ { \ printf("4: %m\n", errno); \ + fflush(stdout); \ return 1; \ } \ } @@ -39,5 +40,5 @@ int main() handle(wait(NULL),-1); printf("0: success!\n"); - return 1; + return 0; } \ No newline at end of file diff --git a/judger/plugins/rlua-54/src/main.rs b/judger/plugins/rlua-54/src/main.rs index 18394ba7..a8ab3f5d 100644 --- a/judger/plugins/rlua-54/src/main.rs +++ b/judger/plugins/rlua-54/src/main.rs @@ -1,9 +1,11 @@ +use std::process::ExitCode; + mod compile; mod execute; mod violate; const LUA_SRC: &str = "/src/code.txt"; -fn main() { +fn main() -> ExitCode { let args: Vec = std::env::args().collect(); let cmd=args.get(1).unwrap().as_str(); @@ -22,4 +24,6 @@ fn main() { "hello" => println!("hello world"), _ => println!("4: Invalid command: \"{}\"", cmd), }; + + ExitCode::from(0) } diff --git a/judger/src/init/config.rs b/judger/src/init/config.rs index 7f2b6dc5..3a3ee4fe 100644 --- a/judger/src/init/config.rs +++ b/judger/src/init/config.rs @@ -6,6 +6,7 @@ use tokio::{fs, io::AsyncReadExt, sync::OnceCell}; pub static CONFIG: OnceCell = OnceCell::const_new(); static CONFIG_PATH: &str = "config/config.toml"; +static CONFIG_DIR: &str = "config"; // config #[derive(Serialize, Deserialize, Debug)] @@ -157,6 +158,8 @@ pub async fn init() { Err(_) => { println!("Unable to find {}, generating default config", CONFIG_PATH); + fs::create_dir_all(CONFIG_DIR).await.unwrap(); + let config: GlobalConfig = toml::from_str("").unwrap(); let config_txt = toml::to_string(&config).unwrap(); diff --git a/judger/src/init/mod.rs b/judger/src/init/mod.rs index 0599746e..176c8c04 100644 --- a/judger/src/init/mod.rs +++ b/judger/src/init/mod.rs @@ -4,12 +4,14 @@ pub mod cgroup; pub mod check; pub mod config; pub mod logger; +pub mod volumn; pub async fn new() { config::init().await; logger::init(); cgroup::init(); check::init(); + volumn::init().await; } #[derive(Error, Debug)] diff --git a/judger/src/init/volumn.rs b/judger/src/init/volumn.rs new file mode 100644 index 00000000..d4525bdc --- /dev/null +++ b/judger/src/init/volumn.rs @@ -0,0 +1,11 @@ +use std::path::Path; + +use tokio::fs; + +use super::config::CONFIG; + +pub async fn init() { + let config = CONFIG.get().unwrap(); + let path: &Path = config.runtime.temp.as_ref(); + fs::create_dir_all(path).await.unwrap(); +} diff --git a/judger/src/langs/artifact.rs b/judger/src/langs/artifact.rs index ddb72b5e..5a79f335 100644 --- a/judger/src/langs/artifact.rs +++ b/judger/src/langs/artifact.rs @@ -216,7 +216,7 @@ impl<'a> CompiledArtifact<'a> { // TODO: We should handle SysError here if !process.succeed() { - // log::debug!("process status: {:?}", process.status); + log::debug!("process status: {:?}", process.status); return Ok(TaskResult::Fail(JudgerCode::Re)); } diff --git a/judger/src/main.rs b/judger/src/main.rs index 7922e903..17fc3614 100644 --- a/judger/src/main.rs +++ b/judger/src/main.rs @@ -8,7 +8,8 @@ pub mod init; pub mod langs; pub mod sandbox; pub mod server; -pub mod test; +#[cfg(test)] +pub mod tests; #[tokio::main] async fn main() { diff --git a/judger/src/test/grpc.rs b/judger/src/tests/grpc.rs similarity index 100% rename from judger/src/test/grpc.rs rename to judger/src/tests/grpc.rs diff --git a/judger/src/test/langs.rs b/judger/src/tests/langs.rs similarity index 92% rename from judger/src/test/langs.rs rename to judger/src/tests/langs.rs index ae23e3cf..5a29dca9 100644 --- a/judger/src/test/langs.rs +++ b/judger/src/tests/langs.rs @@ -5,11 +5,15 @@ use crate::{grpc::prelude::JudgeMatchRule, init::config::CONFIG, langs::prelude: async fn test_hello_world(factory: &mut ArtifactFactory, uuid: Uuid, code: &[u8]) { let mut compiled = factory.compile(&uuid, code).await.unwrap(); + assert!(compiled.get_expection().is_none()); + let mut result = compiled .judge(b"", 1000 * 1000, 1024 * 1024 * 128) .await .unwrap(); + assert!(compiled.get_expection().is_none()); + assert!(result.assert(b"hello world", JudgeMatchRule::SkipSnl)); } #[tokio::test] diff --git a/judger/src/test/mod.rs b/judger/src/tests/mod.rs similarity index 54% rename from judger/src/test/mod.rs rename to judger/src/tests/mod.rs index da4165be..0e581df6 100644 --- a/judger/src/test/mod.rs +++ b/judger/src/tests/mod.rs @@ -1,6 +1,3 @@ -#[cfg(test)] pub mod grpc; -#[cfg(test)] pub mod langs; -#[cfg(test)] pub mod sandbox; diff --git a/judger/src/test/sandbox.rs b/judger/src/tests/sandbox.rs similarity index 100% rename from judger/src/test/sandbox.rs rename to judger/src/tests/sandbox.rs diff --git a/proto/backend.proto b/proto/backend.proto index 23ded15a..67cfa6c3 100644 --- a/proto/backend.proto +++ b/proto/backend.proto @@ -116,14 +116,9 @@ message SubmitStatus { } message CreateSubmitRequest { - message Info { - required bytes code = 1; - required ProblemId problem_id = 2; - required string lang = 3; - } - required Info info = 1; - // required SubmitId id = 2; - required ProblemId problem = 3; + required string lang = 3; + required ProblemId problem_id = 2; + required bytes code = 1; required string request_id = 4; } diff --git a/frontend/rust-toolchain.toml b/rust-toolchain.toml similarity index 100% rename from frontend/rust-toolchain.toml rename to rust-toolchain.toml diff --git a/testsuit/.gitignore b/testsuit/.gitignore index 068ac940..572bef06 100644 --- a/testsuit/.gitignore +++ b/testsuit/.gitignore @@ -1,2 +1,3 @@ /.cargo -*.sqlite \ No newline at end of file +*.sqlite +/jaeger* \ No newline at end of file diff --git a/testsuit/Cargo.toml b/testsuit/Cargo.toml index abefd32c..96b97d0f 100644 --- a/testsuit/Cargo.toml +++ b/testsuit/Cargo.toml @@ -6,6 +6,7 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +toml = { workspace = true } log = "0.4.18" chrono = "0.4.26" futures = "0.3.29" @@ -17,11 +18,14 @@ tonic-web = { workspace = true } futures-core = "0.3.29" http-body = "0.4.6" thiserror = "1.0.56" -rstest = "0.18.2" +clap = { version = "4.4.18", features = ["derive"] } +indicatif-log-bridge = "0.2.2" +indicatif = "0.17.7" +pretty_env_logger = "0.5.0" -[dependencies.cached] -version = "0.47.0" -features = ["async"] +[dependencies.serde] +workspace = true +features = ["derive"] [dependencies.uuid] version = "1.7.0" diff --git a/testsuit/data.toml b/testsuit/data.toml new file mode 100644 index 00000000..b501bd8d --- /dev/null +++ b/testsuit/data.toml @@ -0,0 +1,8 @@ +step = 3 +problem = 1 +testcase = 1 +contest = 1 +user = 2 + +[admin_token] +signature = "1JbkPMNnHWalCSdQnX+/Syupx2M" diff --git a/testsuit/src/checks.rs b/testsuit/src/checks.rs new file mode 100644 index 00000000..8d0de55f --- /dev/null +++ b/testsuit/src/checks.rs @@ -0,0 +1,33 @@ +use std::process::{self, Child, Command, Stdio}; + +use std::path::Path; + +static JAEGER: &str = "./jaeger-all-in-one"; +pub struct JaegerGuard { + child: Child, +} + +impl Drop for JaegerGuard { + fn drop(&mut self) { + self.child.kill().unwrap(); + } +} +pub fn jaeger() -> JaegerGuard { + let path = Path::new(JAEGER); + if !path.exists() { + log::error!("{} not found, please download manualy", JAEGER); + process::exit(1); + } + let mut cmd = Command::new(JAEGER); + cmd.stdin(Stdio::piped()); + cmd.stderr(Stdio::piped()); + cmd.stdout(Stdio::piped()); + let child = cmd.spawn().unwrap(); + log::info!("jaeger is running, see http://localhost:16686/"); + + JaegerGuard { child } +} + +pub fn config() { + log::warn!("TODO: add config check."); +} diff --git a/testsuit/src/client.rs b/testsuit/src/client.rs index 86fd4ec3..d06fd658 100644 --- a/testsuit/src/client.rs +++ b/testsuit/src/client.rs @@ -1,8 +1,6 @@ -use std::{borrow::BorrowMut, fmt}; - use http_body::combinators::UnsyncBoxBody; use hyper::{client::HttpConnector, header::HeaderValue, Request}; -use tonic::{metadata::MetadataValue, IntoRequest}; + use tonic_web::{GrpcWebCall, GrpcWebClientLayer, GrpcWebClientService}; use tower::{Layer, Service}; @@ -57,7 +55,6 @@ pub fn connect_with_token(token: String) -> GrpcWebClientService GrpcWebClientService GrpcWebClientService { let client = hyper::Client::builder().build_http(); - tower::ServiceBuilder::new() .layer(GrpcWebClientLayer::new()) .service(client) diff --git a/testsuit/src/constant.rs b/testsuit/src/constants.rs similarity index 74% rename from testsuit/src/constant.rs rename to testsuit/src/constants.rs index ca9352a8..05beea1f 100644 --- a/testsuit/src/constant.rs +++ b/testsuit/src/constants.rs @@ -1,3 +1,4 @@ pub static SERVER: &str = "http://localhost:8081"; pub static ADMIN: &str = "admin"; pub static ADMIN_PWD: &str = "admin"; +pub static DATA_PATH: &str = "data.toml"; diff --git a/testsuit/src/empty/mod.rs b/testsuit/src/empty/mod.rs deleted file mode 100644 index d6daad29..00000000 --- a/testsuit/src/empty/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! test (and fixture) contain empty dataset(database is mainly unchange during the test), -//! except short-lived data(eg. token) and user -//! -//! The purpose of empty test is to ensure basic functionality - -pub mod login; -pub mod problem; diff --git a/testsuit/src/list/mod.rs b/testsuit/src/list/mod.rs deleted file mode 100644 index e69de29b..00000000 diff --git a/testsuit/src/macro_tool.rs b/testsuit/src/macro_tool.rs new file mode 100644 index 00000000..ae406874 --- /dev/null +++ b/testsuit/src/macro_tool.rs @@ -0,0 +1,15 @@ +#[macro_export] +macro_rules! assert_error { + ($assert:expr,$msg:expr) => { + if !$assert { + return Err(Error::AssertFail($msg)); + } + }; +} + +#[macro_export] +macro_rules! assert_eq_error { + ($left:expr,$right:expr,$msg:expr) => { + $crate::assert_error!($left == $right, $msg) + }; +} diff --git a/testsuit/src/main.rs b/testsuit/src/main.rs index 5a970451..a78b5643 100644 --- a/testsuit/src/main.rs +++ b/testsuit/src/main.rs @@ -1,11 +1,73 @@ -#![allow(unused_variables)] -#![allow(unused_imports)] -#![allow(dead_code)] - +pub mod checks; pub mod client; -pub mod constant; -pub mod empty; +pub mod constants; pub mod grpc; -pub mod prepare; +pub mod macro_tool; +pub mod tests; + +use std::time::Duration; + +use clap::Parser; +use indicatif::ProgressBar; + +use async_std::{ + io::{self, ReadExt}, + task::sleep, +}; + +/// testsuit for backend/judger +#[derive(Parser, Debug)] +#[command(author, about, long_about = None)] +struct Args { + /// force restart + #[arg(long, default_value_t = false)] + force_restart: bool, + /// check backend/judger config + #[arg(long, default_value_t = false)] + config: bool, + /// run jaeger + #[arg(long, default_value_t = true)] + jaeger: bool, +} + +#[async_std::main] +async fn main() { + let args = Args::parse(); + + let mut state = tests::State::load().await; + if args.force_restart { + state.step = 0; + } + + let logger = pretty_env_logger::formatted_builder().build(); + + indicatif_log_bridge::LogWrapper::new(state.bar.clone(), logger) + .try_init() + .unwrap(); + + let pb = state.bar.add(ProgressBar::new(2)); + pb.set_message("checking config"); + + let jaeger = if args.jaeger { + pb.inc(1); + Some(checks::jaeger()) + } else { + None + }; + if args.config { + pb.inc(1); + checks::config(); + } + pb.finish_and_clear(); + + let state = tests::run(state).await; -fn main() {} + state.save().await; + if args.jaeger { + let mut stdin = io::stdin(); + log::info!("Please check telemetry or enter anything to continue..."); + sleep(Duration::from_secs(1)).await; + let _ = stdin.read(&mut [0u8]).await.unwrap(); + } + drop(jaeger) +} diff --git a/testsuit/src/prepare/mod.rs b/testsuit/src/prepare/mod.rs deleted file mode 100644 index 914bf7fb..00000000 --- a/testsuit/src/prepare/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -//! test (and fixture) to create dataset. -//! -//! Include verification of role logic -//! -//! Does not include verification of data existance nor data accessibility - -pub mod problem; diff --git a/testsuit/src/tests/add_to/mod.rs b/testsuit/src/tests/add_to/mod.rs new file mode 100644 index 00000000..745d4dad --- /dev/null +++ b/testsuit/src/tests/add_to/mod.rs @@ -0,0 +1,25 @@ +use serde::{Deserialize, Serialize}; +use tonic::async_trait; + +use super::{ui::UI, Error, State}; + +pub mod problem; + +#[derive(Serialize, Deserialize)] +pub struct StartOfId(pub i32); + +pub struct Test; + +#[async_trait] +impl super::Test for Test { + type Error = Error; + const NAME: &'static str = "add * to *"; + async fn run(state: &mut State) -> Result<(), Self::Error> { + let mut ui = UI::new(&state.bar, 1); + + ui.inc("create problem"); + problem::testcase(state).await?; + + Ok(()) + } +} diff --git a/testsuit/src/tests/add_to/problem.rs b/testsuit/src/tests/add_to/problem.rs new file mode 100644 index 00000000..05119076 --- /dev/null +++ b/testsuit/src/tests/add_to/problem.rs @@ -0,0 +1,30 @@ +use crate::{ + client::connect_with_token, + constants::SERVER, + grpc::backend::{ + testcase_set_client::TestcaseSetClient, AddTestcaseToProblemRequest, ProblemId, TestcaseId, + }, + tests::{Error, State}, +}; + +pub async fn testcase(state: &mut State) -> Result<(), Error> { + let mut client = TestcaseSetClient::with_origin( + connect_with_token(state.admin_token.as_ref().unwrap().signature.clone()), + SERVER.try_into().unwrap(), + ); + + for i in 0..3 { + client + .add_to_problem(AddTestcaseToProblemRequest { + testcase_id: TestcaseId { + id: state.testcase.as_ref().unwrap().0 + i, + }, + problem_id: ProblemId { + id: state.problem.as_ref().unwrap().0, + }, + }) + .await?; + } + + Ok(()) +} diff --git a/testsuit/src/tests/create/contest.rs b/testsuit/src/tests/create/contest.rs new file mode 100644 index 00000000..cf877bb0 --- /dev/null +++ b/testsuit/src/tests/create/contest.rs @@ -0,0 +1,42 @@ +use std::str::FromStr; + +use crate::{ + client::connect_with_token, + grpc::backend::{ + contest_set_client::ContestSetClient, create_contest_request, CreateContestRequest, + }, + tests::{Error, State}, +}; +use prost_types::Timestamp; +use uuid::Uuid; + +use crate::constants::SERVER; + +use super::StartOfId; + +pub async fn create(state: &mut State) -> Result<(), Error> { + let mut client = ContestSetClient::with_origin( + connect_with_token(state.admin_token.as_ref().unwrap().signature.clone()), + SERVER.try_into().unwrap(), + ); + + let res = client + .create(CreateContestRequest { + info: create_contest_request::Info { + title: "testing contest".to_string(), + begin: Timestamp::from_str("1970-01-01T00:00:00Z").unwrap(), + end: Timestamp::from_str("2050-01-01T00:00:00Z").unwrap(), + tags: "testsuit search_filter_1 search_filter_2".to_owned(), + content: "THIS IS A TESTING CONTEST, seeing this in your production deployment is dangerous.\nshould not be search".to_owned(), + password: Some("password".to_owned()), + }, + request_id: Uuid::new_v4().to_string(), + }) + .await + .unwrap(); + + let res = res.into_inner(); + state.contest = Some(StartOfId(res.id)); + + Ok(()) +} diff --git a/testsuit/src/tests/create/mod.rs b/testsuit/src/tests/create/mod.rs new file mode 100644 index 00000000..e8d0ab59 --- /dev/null +++ b/testsuit/src/tests/create/mod.rs @@ -0,0 +1,40 @@ +//! test (and fixture) to create dataset. +//! +//! Include verification of role logic +//! +//! Does not include verification of data existance nor data accessibility + +use serde::{Deserialize, Serialize}; +use tonic::async_trait; + +use super::{ui::UI, Error, State}; + +pub mod contest; +pub mod problem; +pub mod testcase; +pub mod user; + +#[derive(Serialize, Deserialize)] +pub struct StartOfId(pub i32); + +pub struct Test; + +#[async_trait] +impl super::Test for Test { + type Error = Error; + const NAME: &'static str = "sample dataset"; + async fn run(state: &mut State) -> Result<(), Self::Error> { + let mut ui = UI::new(&state.bar, 4); + + ui.inc("create problem"); + problem::create(state).await?; + ui.inc("create testcase"); + testcase::create(state).await?; + ui.inc("create contest"); + contest::create(state).await?; + ui.inc("create user"); + user::create(state).await?; + + Ok(()) + } +} diff --git a/testsuit/src/prepare/problem.rs b/testsuit/src/tests/create/problem.rs similarity index 58% rename from testsuit/src/prepare/problem.rs rename to testsuit/src/tests/create/problem.rs index b8d4e2c2..6b10d5b3 100644 --- a/testsuit/src/prepare/problem.rs +++ b/testsuit/src/tests/create/problem.rs @@ -1,31 +1,23 @@ -use std::fmt::format; - use crate::{ + assert_eq_error, client::connect_with_token, - empty::login::admin_token, grpc::backend::{create_problem_request, MatchRule}, + tests::{Error, State}, }; -use async_std::task; -use cached::proc_macro::cached; -use rstest::*; -use tonic::{metadata::MetadataValue, transport::Channel, Code, Request}; use uuid::Uuid; use crate::{ - client::connect, - constant::SERVER, + constants::SERVER, grpc::backend::{problem_set_client::ProblemSetClient, CreateProblemRequest}, }; -#[rstest] -async fn create_problem(#[future] admin_token: String) { - create_problem_inner(admin_token.await).await -} +use super::StartOfId; -#[cached] -async fn create_problem_inner(admin_token: String) { - let mut client = - ProblemSetClient::with_origin(connect_with_token(admin_token), SERVER.try_into().unwrap()); +pub async fn create(state: &mut State) -> Result<(), Error> { + let mut client = ProblemSetClient::with_origin( + connect_with_token(state.admin_token.as_ref().unwrap().signature.clone()), + SERVER.try_into().unwrap(), + ); let mut last = None; for secquence in 1..11 { @@ -37,19 +29,22 @@ async fn create_problem_inner(admin_token: String) { time: 1000 * 1000, memory: 1024 * 1024 * 128, tags: "problem test".to_owned(), - content: format!("description for problem {}", secquence), - match_rule: MatchRule::ExactSame as i32, + content: format!("description for problem {}\nInputs: x,y,z separated by space\nOutput: x+y+z", secquence), + match_rule: MatchRule::IgnoreSnl as i32, order: 0.01 * ((1 + secquence) as f32), }, request_id: Uuid::new_v4().to_string(), }) - .await - .unwrap(); +.await?; let res = res.into_inner(); if let Some(x) = last { - assert_eq!(x + 1, res.id); + assert_eq_error!(x + 1, res.id, "id generator should be sequential"); + } else { + state.problem = Some(StartOfId(res.id)); } last = Some(res.id); } + + Ok(()) } diff --git a/testsuit/src/tests/create/testcase.rs b/testsuit/src/tests/create/testcase.rs new file mode 100644 index 00000000..8ab18034 --- /dev/null +++ b/testsuit/src/tests/create/testcase.rs @@ -0,0 +1,44 @@ +use crate::{ + assert_eq_error, + client::connect_with_token, + grpc::backend::{ + create_testcase_request, testcase_set_client::TestcaseSetClient, CreateTestcaseRequest, + }, + tests::{Error, State}, +}; +use uuid::Uuid; + +use crate::constants::SERVER; + +use super::StartOfId; + +pub async fn create(state: &mut State) -> Result<(), Error> { + let mut client = TestcaseSetClient::with_origin( + connect_with_token(state.admin_token.as_ref().unwrap().signature.clone()), + SERVER.try_into().unwrap(), + ); + + let mut last = None; + for secquence in 1..11 { + let res = client + .create(CreateTestcaseRequest { + info: create_testcase_request::Info { + score: secquence, + input: b"2 3 4".to_vec(), + output: b"10".to_vec(), + }, + request_id: Uuid::new_v4().to_string(), + }) + .await?; + + let res = res.into_inner(); + if let Some(x) = last { + assert_eq_error!(x + 1, res.id, "id generator should be sequential"); + } else { + state.testcase = Some(StartOfId(res.id)); + } + last = Some(res.id); + } + + Ok(()) +} diff --git a/testsuit/src/tests/create/user.rs b/testsuit/src/tests/create/user.rs new file mode 100644 index 00000000..a1545ba4 --- /dev/null +++ b/testsuit/src/tests/create/user.rs @@ -0,0 +1,42 @@ +use crate::{ + assert_eq_error, + client::connect_with_token, + grpc::backend::{create_user_request, user_set_client::UserSetClient, CreateUserRequest, Role}, + tests::{Error, State}, +}; +use uuid::Uuid; + +use crate::constants::SERVER; + +use super::StartOfId; + +pub async fn create(state: &mut State) -> Result<(), Error> { + let mut client = UserSetClient::with_origin( + connect_with_token(state.admin_token.as_ref().unwrap().signature.clone()), + SERVER.try_into().unwrap(), + ); + + let mut last = None; + for secquence in 1..3 { + let res = client + .create(CreateUserRequest { + info: create_user_request::Info { + username: format!("user{}", secquence), + password: secquence.to_string(), + role: Role::User as i32, + }, + request_id: Uuid::new_v4().to_string(), + }) + .await?; + + let res = res.into_inner(); + if let Some(x) = last { + assert_eq_error!(x + 1, res.id, "id generator should be sequential"); + } else { + state.user = Some(StartOfId(res.id)); + } + last = Some(res.id); + } + + Ok(()) +} diff --git a/testsuit/src/empty/login.rs b/testsuit/src/tests/empty/login.rs similarity index 54% rename from testsuit/src/empty/login.rs rename to testsuit/src/tests/empty/login.rs index 39f500cb..725308cf 100644 --- a/testsuit/src/empty/login.rs +++ b/testsuit/src/tests/empty/login.rs @@ -1,20 +1,18 @@ -use async_std::task; -use cached::proc_macro::cached; -use rstest::*; - +use super::Error; use crate::{ + assert_eq_error, client::connect, - constant::*, + constants::*, grpc::backend::{token_set_client::TokenSetClient, LoginRequest, Role}, }; +use serde::{Deserialize, Serialize}; -#[fixture] -pub async fn admin_token() -> String { - inner_admin_token().await +#[derive(Serialize, Deserialize)] +pub struct AdminToken { + pub signature: String, } -#[cached] -pub async fn inner_admin_token() -> String { +pub async fn login() -> Result { let mut client = TokenSetClient::with_origin(connect(), SERVER.try_into().unwrap()); let res = client @@ -28,12 +26,9 @@ pub async fn inner_admin_token() -> String { let res = res.into_inner(); - assert_eq!(res.role(), Role::Root); - - res.token.signature -} + assert_eq_error!(res.role(), Role::Root, "admin@admin login fail"); -#[rstest] -async fn test(#[future] admin_token: String) { - admin_token.await; + Ok(AdminToken { + signature: res.token.signature, + }) } diff --git a/testsuit/src/tests/empty/mod.rs b/testsuit/src/tests/empty/mod.rs new file mode 100644 index 00000000..0d1199e7 --- /dev/null +++ b/testsuit/src/tests/empty/mod.rs @@ -0,0 +1,35 @@ +//! test (and fixture) contain empty dataset(database is mainly unchange during the test), +//! except short-lived data(eg. token) and user +//! +//! The purpose of empty test is to ensure basic functionality + +use tonic::{async_trait, Code}; + +pub use super::Error; +use super::{ui::UI, State}; + +pub mod login; +pub mod problem; + +pub struct Test; + +#[async_trait] +impl super::Test for Test { + type Error = Error; + const NAME: &'static str = "Empty dataset"; + async fn run(state: &mut State) -> Result<(), Self::Error> { + let mut ui = UI::new(&state.bar, 3); + + ui.inc("list problem(1)"); + problem::list(1, Code::OutOfRange).await?; + ui.inc("list problem(2)"); + problem::list(1000, Code::InvalidArgument).await?; + + ui.inc("admin login"); + let token = login::login().await?; + + state.admin_token = Some(token); + + Ok(()) + } +} diff --git a/testsuit/src/empty/problem.rs b/testsuit/src/tests/empty/problem.rs similarity index 69% rename from testsuit/src/empty/problem.rs rename to testsuit/src/tests/empty/problem.rs index 3eb94609..7a15f497 100644 --- a/testsuit/src/empty/problem.rs +++ b/testsuit/src/tests/empty/problem.rs @@ -1,20 +1,19 @@ -use async_std::task; -use rstest::*; +use super::Error; use tonic::Code; use crate::{ + assert_eq_error, client::connect, - constant::SERVER, + constants::SERVER, grpc::backend::{ list_by_request, list_problem_request, problem_set_client::ProblemSetClient, ListProblemRequest, ProblemSortBy, }, }; -#[rstest] -#[case::not_found(1, Code::NotFound)] -#[case::large_number(1000, Code::InvalidArgument)] -async fn list_problem(#[case] size: u64, #[case] code: Code) { +// #[case::not_found(1, Code::NotFound)] +// #[case::large_number(1000, Code::InvalidArgument)] +pub async fn list(size: u64, code: Code) -> Result<(), Error> { let mut client = ProblemSetClient::with_origin(connect(), SERVER.try_into().unwrap()); let res = client @@ -32,13 +31,13 @@ async fn list_problem(#[case] size: u64, #[case] code: Code) { let err = res.unwrap_err(); - assert_eq!(err.code(), code) + assert_eq_error!(err.code(), code, "list_problem should error"); + Ok(()) } -#[rstest] -#[case::not_found(1, Code::NotFound)] -#[case::large_number(1000, Code::InvalidArgument)] -async fn list_problem_by_contest(#[case] size: u64, #[case] code: Code) { +// #[case::not_found(1, Code::NotFound)] +// #[case::large_number(1000, Code::InvalidArgument)] +pub async fn list_by(size: u64, code: Code) -> Result<(), Error> { let mut client = ProblemSetClient::with_origin(connect(), SERVER.try_into().unwrap()); let res = client @@ -55,5 +54,6 @@ async fn list_problem_by_contest(#[case] size: u64, #[case] code: Code) { let err = res.unwrap_err(); - assert_eq!(err.code(), code) + assert_eq_error!(err.code(), code, "list_problem should error"); + Ok(()) } diff --git a/testsuit/src/tests/mod.rs b/testsuit/src/tests/mod.rs new file mode 100644 index 00000000..b5c6d555 --- /dev/null +++ b/testsuit/src/tests/mod.rs @@ -0,0 +1,87 @@ +pub mod add_to; +pub mod create; +pub mod empty; +pub mod operate; +pub mod ui; + +use std::path::Path; + +use async_std::fs; +use indicatif::*; +use serde::{Deserialize, Serialize}; +use tonic::async_trait; + +use self::{create::StartOfId, ui::UI}; + +#[derive(thiserror::Error, Debug)] +pub enum Error { + #[error("assert fail `{0}`")] + AssertFail(&'static str), + #[error("expect rpc to success: `{0}`")] + Tonic(#[from] tonic::Status), +} + +#[derive(Default, Serialize, Deserialize)] +pub struct State { + pub step: u64, + #[serde(skip_deserializing, skip_serializing)] + pub bar: MultiProgress, + pub admin_token: Option, + pub problem: Option>, + pub testcase: Option>, + pub contest: Option>, + pub user: Option>, +} +// all testcase was added to the first problem +// the second and third problem was added to the only contest + +impl State { + pub async fn load() -> Self { + let path = Path::new(crate::constants::DATA_PATH); + match path.exists() { + true => { + let raw = fs::read(path).await.unwrap(); + toml::from_str(&String::from_utf8_lossy(&raw)).unwrap() + } + false => State::default(), + } + } + pub async fn save(self) { + let path = Path::new(crate::constants::DATA_PATH); + let raw = toml::to_string_pretty(&self).unwrap(); + fs::write(path, raw).await.unwrap(); + } +} + +#[async_trait] +pub trait Test { + type Error: std::error::Error; + const NAME: &'static str; + async fn run(state: &mut State) -> Result<(), Self::Error>; +} + +pub async fn run(mut state: State) -> State { + let mut ui = UI::new(&state.bar, 3); + + macro_rules! handle { + ($cc:expr,$e:ident) => { + if ($cc)==state.step{ + log::info!("step {}",state.step); + ui.inc($e::Test::NAME); + if let Err(err)=$e::Test::run(&mut state).await{ + log::error!("Error at {}, test stop, progress saved!",err); + return state; + } + state.step+=1; + } + }; + ($cc:expr,$x:ident, $($y:ident),+)=>{ + handle!($cc,$x); + handle!($cc+1,$($y),+); + } + } + + handle!(0, empty, create, add_to, operate); + state.bar.clear().unwrap(); + state +} diff --git a/testsuit/src/tests/operate/admin.rs b/testsuit/src/tests/operate/admin.rs new file mode 100644 index 00000000..7a333689 --- /dev/null +++ b/testsuit/src/tests/operate/admin.rs @@ -0,0 +1,50 @@ +use std::time::Duration; + +use crate::{ + assert_eq_error, + client::connect_with_token, + grpc::backend::{ + submit_set_client::SubmitSetClient, CreateSubmitRequest, ProblemId, StateCode, + }, + tests::{Error, State}, +}; +use async_std::task::sleep; + +use uuid::Uuid; + +use crate::constants::SERVER; + +static CODE: &[u8] = + b"a=io.read(\"*n\")\nb=io.read(\"*n\")\nc=io.read(\"*n\")\nio.write(tostring((a+b+c)))\n"; + +pub async fn submit(state: &mut State) -> Result<(), Error> { + let mut client = SubmitSetClient::with_origin( + connect_with_token(state.admin_token.as_ref().unwrap().signature.clone()), + SERVER.try_into().unwrap(), + ); + + let res = client + .create(CreateSubmitRequest { + lang: "1c41598f-e253-4f81-9ef5-d50bf1e4e74f".to_owned(), + problem_id: ProblemId { + id: state.problem.as_mut().unwrap().0, + }, + code: CODE.to_vec(), + request_id: Uuid::new_v4().to_string(), + }) + .await? + .into_inner(); + + // FIXME: follow was omit because we can't sleep in lua + // There is no os binding in default lua plugin + + // let res=client.follow(res).await?.into_inner(); + + sleep(Duration::from_millis(500)).await; + + let res = client.info(res).await?.into_inner(); + + assert_eq_error!(res.state.code, StateCode::Ac as i32, "should AC"); + + Ok(()) +} diff --git a/testsuit/src/tests/operate/mod.rs b/testsuit/src/tests/operate/mod.rs new file mode 100644 index 00000000..dbfdec05 --- /dev/null +++ b/testsuit/src/tests/operate/mod.rs @@ -0,0 +1,21 @@ +pub mod admin; + +use tonic::async_trait; + +use super::{ui::UI, Error, State}; + +pub struct Test; + +#[async_trait] +impl super::Test for Test { + type Error = Error; + const NAME: &'static str = "simulate user behavior"; + async fn run(state: &mut State) -> Result<(), Self::Error> { + let mut ui = UI::new(&state.bar, 1); + + ui.inc("submit admin"); + admin::submit(state).await?; + + Ok(()) + } +} diff --git a/testsuit/src/tests/ui.rs b/testsuit/src/tests/ui.rs new file mode 100644 index 00000000..13c1a71e --- /dev/null +++ b/testsuit/src/tests/ui.rs @@ -0,0 +1,41 @@ +use indicatif::*; + +pub struct UI { + pb: ProgressBar, + len: u64, + progress: u64, +} + +impl Drop for UI { + fn drop(&mut self) { + self.pb.finish(); + } +} + +impl UI { + pub fn new(m: &MultiProgress, len: u64) -> Self { + let pb = m.add(ProgressBar::new_spinner()); + + pb.set_message(""); + pb.set_prefix(format!("[0/{}]", len)); + + let style = ProgressStyle::with_template("{prefix:.bold.dim} {spinner} Running {wide_msg}") + .unwrap() + .tick_chars("⠁⠂⠄⡀⢀⠠⠐⠈ "); + + pb.set_style(style); + + Self { + pb, + len, + progress: 0, + } + } + pub fn inc(&mut self, msg: &'static str) { + log::warn!("ui inc"); + self.progress += 1; + self.pb.set_message(msg); + self.pb + .set_prefix(format!("[{}/{}]", self.progress, self.len)); + } +}