mirror of
https://github.com/dexoron/dcr.git
synced 2026-10-21 12:28:30 +00:00
feat: Update version to 0.4.0, add test command, and improve documentation for testing functionality
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -90,7 +90,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "dcr"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"glob",
|
||||
"reqwest",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dcr"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ dcr build --debug --clean
|
||||
- Incremental rebuild checks source/object mtime and tracked header dependencies.
|
||||
- `--force` skips build cache checks and recompiles.
|
||||
- `--clean` removes `target/<profile>` and `build.clean` paths before building.
|
||||
- Default GCC/Clang profile flags: `debug` -> `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDCR_DEBUG`, `release` -> `-O3 -DNDEBUG`.
|
||||
- For `language = "asm"` with `compiler = "as"`/`"gas"`, use `.s` files (no preprocessing). For `.S`, use `gcc` or `clang`.
|
||||
- In workspace root, `dcr build` builds all members in dependency order.
|
||||
- `build.exclude` removes paths from source/header collection; `build.include` re-allows matching paths and has priority over `exclude`.
|
||||
|
||||
@@ -89,6 +89,7 @@ Usage in VSCode:
|
||||
- Open the Command Palette (`Ctrl+Shift+P`) and select **"Debug: Start Debugging"** to run debugger
|
||||
- Press `Ctrl+Shift+B` to run the build task
|
||||
- Clangd will provide IntelliSense and diagnostics based on `compile_commands.json`
|
||||
- Generated `launch.json` uses CodeLLDB (`type: "lldb"`), so install `vadimcn.vscode-lldb`
|
||||
|
||||
### `dcr gen clion`
|
||||
|
||||
@@ -152,6 +153,7 @@ When running `dcr gen` in a workspace root, all commands process both the root p
|
||||
- Verify binary paths in `launch.json` match actual build output (`target/<profile>/binary-name`)
|
||||
- For workspace projects, each member's debug config uses its own `target/` directory
|
||||
- Ensure GDB/LLDB is installed: `gdb --version` or `lldb --version`
|
||||
- If VSCode reports unsupported debugger type, install the extension that matches `launch.json` type (`vadimcn.vscode-lldb` for `lldb`)
|
||||
|
||||
### CLion not recognizing includes
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ You can also add `--force` or `--clean` to `build`/`run`.
|
||||
### GCC/Clang-like toolchains
|
||||
|
||||
- `debug`: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDCR_DEBUG`
|
||||
- `release`: `-O3 -DNDEBUG -march=native`
|
||||
- `release`: `-O3 -DNDEBUG`
|
||||
|
||||
### MSVC toolchain
|
||||
|
||||
@@ -40,7 +40,7 @@ Default compiler flags:
|
||||
GCC/Clang:
|
||||
|
||||
- `debug`: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDCR_DEBUG`
|
||||
- `release`: `-O3 -DNDEBUG -march=native`
|
||||
- `release`: `-O3 -DNDEBUG`
|
||||
|
||||
MSVC:
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ These flags are applied automatically based on profile and compiler backend.
|
||||
GCC/Clang:
|
||||
|
||||
- `debug`: `-O0 -g -Wall -Wextra -fno-omit-frame-pointer -DDCR_DEBUG`
|
||||
- `release`: `-O3 -DNDEBUG -march=native`
|
||||
- `release`: `-O3 -DNDEBUG`
|
||||
|
||||
MSVC:
|
||||
|
||||
|
||||
@@ -801,7 +801,6 @@ fn build_compile_command(info: &ProjectInfo, source: &str, profile: &str) -> Vec
|
||||
"release" => {
|
||||
cmd.push("-O3".to_string());
|
||||
cmd.push("-DNDEBUG".to_string());
|
||||
cmd.push("-march=native".to_string());
|
||||
}
|
||||
"debug" => {
|
||||
cmd.push("-O0".to_string());
|
||||
@@ -922,7 +921,8 @@ fn gen_vscode(args: &[String]) -> i32 {
|
||||
fn gen_extensions_json() -> String {
|
||||
r#"{
|
||||
"recommendations": [
|
||||
"llvm-vs-code-extensions.vscode-clangd"
|
||||
"llvm-vs-code-extensions.vscode-clangd",
|
||||
"vadimcn.vscode-lldb"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools",
|
||||
@@ -1025,15 +1025,13 @@ fn gen_launch_json(projects: &[ProjectInfo], _root: &Path) -> String {
|
||||
let debug_entry = format!(
|
||||
r#" {{
|
||||
"name": {name},
|
||||
"type": "cppdbg",
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"program": {prog},
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"stopOnEntry": false,
|
||||
"cwd": {cwd},
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"terminal": "integrated",
|
||||
"preLaunchTask": "dcr: build (debug)"
|
||||
}}"#,
|
||||
name = json_str(&format!("{} (debug)", info.name)),
|
||||
@@ -1044,15 +1042,13 @@ fn gen_launch_json(projects: &[ProjectInfo], _root: &Path) -> String {
|
||||
let release_entry = format!(
|
||||
r#" {{
|
||||
"name": {name},
|
||||
"type": "cppdbg",
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"program": {prog},
|
||||
"args": [],
|
||||
"stopAtEntry": false,
|
||||
"stopOnEntry": false,
|
||||
"cwd": {cwd},
|
||||
"environment": [],
|
||||
"externalConsole": false,
|
||||
"MIMode": "gdb",
|
||||
"terminal": "integrated",
|
||||
"preLaunchTask": "dcr: build (release)"
|
||||
}}"#,
|
||||
name = json_str(&format!("{} (release)", info.name)),
|
||||
@@ -1069,7 +1065,7 @@ fn gen_launch_json(projects: &[ProjectInfo], _root: &Path) -> String {
|
||||
configs.push(
|
||||
r#" {
|
||||
"name": "(placeholder — no binary targets found)",
|
||||
"type": "cppdbg",
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"program": "",
|
||||
"cwd": "${workspaceFolder}"
|
||||
|
||||
@@ -12,6 +12,7 @@ pub fn help() -> i32 {
|
||||
println!(" init Initialize the current directory as a project");
|
||||
println!(" build [--profile] Build the project (default: --debug)");
|
||||
println!(" run [--profile] Build and run the project (default: --debug)");
|
||||
println!(" test Run project tests (alias: tests)");
|
||||
println!(" clean Remove the target directory");
|
||||
println!(" gen <subcommand> Generate IDE integration files");
|
||||
printc("FLAGS:", BOLD_GREEN);
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::config::FILE_MAIN_C;
|
||||
use crate::core::config::Config;
|
||||
use crate::utils::fs::check_dir;
|
||||
use crate::utils::log::{error, warn};
|
||||
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
|
||||
use crate::utils::text::{colored, printc, BOLD_CYAN, BOLD_GREEN};
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use toml::Value;
|
||||
@@ -65,7 +65,7 @@ pub fn init(args: &[String]) -> i32 {
|
||||
return 1;
|
||||
}
|
||||
println!(
|
||||
" {} Created file {}\n",
|
||||
" {} Created file {}",
|
||||
colored("✔", BOLD_GREEN),
|
||||
colored("src/main.c", BOLD_CYAN)
|
||||
);
|
||||
|
||||
@@ -7,3 +7,4 @@ pub mod help;
|
||||
pub mod init;
|
||||
pub mod new;
|
||||
pub mod run;
|
||||
pub mod test;
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::config::FILE_MAIN_C;
|
||||
use crate::core::config::Config;
|
||||
use crate::utils::fs::check_dir;
|
||||
use crate::utils::log::{error, warn};
|
||||
use crate::utils::text::{BOLD_CYAN, BOLD_GREEN, colored, printc};
|
||||
use crate::utils::text::{colored, printc, BOLD_CYAN, BOLD_GREEN};
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use toml::Value;
|
||||
@@ -86,7 +86,7 @@ pub fn new(args: &[String]) -> i32 {
|
||||
return 1;
|
||||
}
|
||||
println!(
|
||||
" {} Created file {}\n",
|
||||
" {} Created file {}",
|
||||
colored("✔", BOLD_GREEN),
|
||||
colored("src/main.c", BOLD_CYAN)
|
||||
);
|
||||
|
||||
260
src/cli/test.rs
Normal file
260
src/cli/test.rs
Normal file
@@ -0,0 +1,260 @@
|
||||
use crate::cli::build;
|
||||
use crate::config::{FILE_DCR_TEST_H, FILE_TEST_C, flags};
|
||||
use crate::core::config::Config;
|
||||
use crate::utils::fs::find_project_root;
|
||||
use crate::utils::log::error;
|
||||
use crate::utils::text::{BOLD_GREEN, BOLD_RED, RESET, colored};
|
||||
use std::env;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
use std::process::Command;
|
||||
|
||||
const BOLD_BLUE: &str = "\x1b[1m\x1b[94m";
|
||||
|
||||
pub fn test(args: &[String]) -> i32 {
|
||||
let mut init_header = false;
|
||||
for arg in args {
|
||||
if arg == "--help" {
|
||||
println!("USAGE:");
|
||||
println!(" dcr test [--init]");
|
||||
println!();
|
||||
println!("ALIASES:");
|
||||
println!(" dcr tests");
|
||||
println!();
|
||||
println!("DESCRIPTION:");
|
||||
println!(" Runs project tests and prints a unified testsuite report.");
|
||||
println!();
|
||||
println!("OPTIONS:");
|
||||
println!(" --init Create tests/dcr_test.h in current project");
|
||||
return 0;
|
||||
}
|
||||
if arg == "--init" {
|
||||
init_header = true;
|
||||
continue;
|
||||
}
|
||||
error("Unknown argument");
|
||||
return 1;
|
||||
}
|
||||
|
||||
let start_dir = match std::env::current_dir() {
|
||||
Ok(dir) => dir,
|
||||
Err(_) => {
|
||||
error("Failed to determine current directory");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
let root = match find_project_root(&start_dir) {
|
||||
Ok(Some(dir)) => dir,
|
||||
Ok(None) => {
|
||||
error("dcr.toml file not found");
|
||||
return 1;
|
||||
}
|
||||
Err(_) => {
|
||||
error("Failed to find project root");
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
if init_header {
|
||||
match with_dir(&root, ensure_test_header) {
|
||||
Ok(()) => {}
|
||||
Err(msg) => {
|
||||
error(&msg);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match with_dir(&root, run_testsuite) {
|
||||
Ok(code) => code,
|
||||
Err(msg) => {
|
||||
error(&msg);
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn run_testsuite() -> Result<i32, String> {
|
||||
if build::build(&[String::from("--release")]) != 0 {
|
||||
return Ok(1);
|
||||
}
|
||||
|
||||
let config = Config::open("./dcr.toml").map_err(|_| "Failed to read dcr.toml".to_string())?;
|
||||
let name = config.get("package.name").and_then(|v| v.as_str()).ok_or("package.name not found in dcr.toml")?;
|
||||
let kind = config.get("package.kind").and_then(|v| v.as_str()).unwrap_or("bin");
|
||||
|
||||
let compiler = env::var("DCR_CC").unwrap_or_else(|_| "cc".to_string());
|
||||
let profile = "release";
|
||||
let flags_vec = flags(profile).unwrap_or(&[]);
|
||||
let flags_str = flags_vec.join(" ");
|
||||
|
||||
let test_c = "./tests/test.c";
|
||||
if !Path::new(test_c).exists() {
|
||||
return Err("tests/test.c not found; run 'dcr test --init' first".to_string());
|
||||
}
|
||||
|
||||
// Compile test.c to object
|
||||
let obj_path = "./tests/test.o";
|
||||
let compile_cmd = format!("{} -c {} -o {} {}", compiler, test_c, obj_path, flags_str);
|
||||
let compile_status = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(&compile_cmd)
|
||||
.status()
|
||||
.map_err(|_| format!("Failed to compile {}", test_c))?;
|
||||
if !compile_status.success() {
|
||||
return Err(format!("Compilation of {} failed", test_c));
|
||||
}
|
||||
|
||||
// Link
|
||||
let bin_path = format!("./tests/test{}", std::env::consts::EXE_SUFFIX);
|
||||
let link_cmd = if kind == "lib" {
|
||||
format!("{} {} -o {} -Ltarget/release -l{}", compiler, obj_path, bin_path, name)
|
||||
} else {
|
||||
format!("{} {} -o {}", compiler, obj_path, bin_path)
|
||||
};
|
||||
let link_status = Command::new("sh")
|
||||
.arg("-c")
|
||||
.arg(&link_cmd)
|
||||
.status()
|
||||
.map_err(|_| format!("Failed to link {}", bin_path))?;
|
||||
if !link_status.success() {
|
||||
return Err(format!("Linking of {} failed", bin_path));
|
||||
}
|
||||
|
||||
let out = Command::new(&bin_path)
|
||||
.output()
|
||||
.map_err(|_| format!("Failed to run `{bin_path}`"))?;
|
||||
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
if !stderr.trim().is_empty() {
|
||||
eprint!("{}", stderr);
|
||||
}
|
||||
|
||||
let declared = extract_test_names("./tests/test.c");
|
||||
let mut pass = 0;
|
||||
let mut skip = 0;
|
||||
let mut fail = 0;
|
||||
let mut parsed_any = false;
|
||||
|
||||
for line in stdout.lines() {
|
||||
let line = line.trim();
|
||||
if let Some(name) = line.strip_prefix("[PASS] ") {
|
||||
println!("{} {}", colored("[PASS]", BOLD_GREEN), name);
|
||||
pass += 1;
|
||||
parsed_any = true;
|
||||
continue;
|
||||
}
|
||||
if let Some(name) = line.strip_prefix("[SKIP] ") {
|
||||
println!("{} {}", colored("[SKIP]", BOLD_BLUE), name);
|
||||
skip += 1;
|
||||
parsed_any = true;
|
||||
continue;
|
||||
}
|
||||
if let Some(name) = line.strip_prefix("[FAIL] ") {
|
||||
println!("{} {}", colored("[FAIL]", BOLD_RED), name);
|
||||
fail += 1;
|
||||
parsed_any = true;
|
||||
continue;
|
||||
}
|
||||
if let Some((status, name)) = line.split_once('\t') {
|
||||
match status {
|
||||
"PASS" => {
|
||||
println!("{} {}", colored("[PASS]", BOLD_GREEN), name);
|
||||
pass += 1;
|
||||
parsed_any = true;
|
||||
}
|
||||
"SKIP" => {
|
||||
println!("{} {}", colored("[SKIP]", BOLD_BLUE), name);
|
||||
skip += 1;
|
||||
parsed_any = true;
|
||||
}
|
||||
"FAIL" => {
|
||||
println!("{} {}", colored("[FAIL]", BOLD_RED), name);
|
||||
fail += 1;
|
||||
parsed_any = true;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !parsed_any {
|
||||
if out.status.success() {
|
||||
for name in &declared {
|
||||
println!("{} {}", colored("[PASS]", BOLD_GREEN), name);
|
||||
}
|
||||
pass = declared.len() as i32;
|
||||
} else {
|
||||
println!("{} testsuite", colored("[FAIL]", BOLD_RED));
|
||||
fail = 1;
|
||||
}
|
||||
}
|
||||
|
||||
let total = if parsed_any || declared.is_empty() {
|
||||
pass + skip + fail
|
||||
} else {
|
||||
declared.len() as i32
|
||||
};
|
||||
|
||||
println!();
|
||||
println!("{}", colored("=====================", BOLD_GREEN));
|
||||
println!("{}", colored(" Testsuite summary ", BOLD_GREEN));
|
||||
println!("{}", colored("=====================", BOLD_GREEN));
|
||||
println!("TOTAL: {}", total);
|
||||
print_field("PASS", pass, BOLD_GREEN);
|
||||
print_field("SKIP", skip, BOLD_BLUE);
|
||||
print_field("FAIL", fail, BOLD_RED);
|
||||
println!("{}", colored("=====================", BOLD_GREEN));
|
||||
|
||||
if fail > 0 || !out.status.success() {
|
||||
return Ok(1);
|
||||
}
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
fn ensure_test_header() -> Result<(), String> {
|
||||
fs::create_dir_all("./tests").map_err(|_| "Failed to create tests/".to_string())?;
|
||||
fs::write("./tests/dcr_test.h", FILE_DCR_TEST_H)
|
||||
.map_err(|_| "Failed to write tests/dcr_test.h".to_string())?;
|
||||
println!("Created tests/dcr_test.h");
|
||||
fs::write("./tests/test.c", FILE_TEST_C)
|
||||
.map_err(|_| "Failed to write tests/test.c".to_string())?;
|
||||
println!("Created tests/test.c");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_test_names(path: &str) -> Vec<String> {
|
||||
let Ok(content) = fs::read_to_string(path) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut names = Vec::new();
|
||||
for line in content.lines() {
|
||||
let trimmed = line.trim();
|
||||
if let Some(rest) = trimmed.strip_prefix("TEST_CASE(")
|
||||
&& let Some(name) = rest.strip_suffix("),")
|
||||
{
|
||||
names.push(name.to_string());
|
||||
}
|
||||
}
|
||||
names
|
||||
}
|
||||
|
||||
fn print_field(label: &str, value: i32, color: &str) {
|
||||
if value == 0 {
|
||||
println!("{}: {}", label, value);
|
||||
} else {
|
||||
println!("{}{}: {}{}", color, label, value, RESET);
|
||||
}
|
||||
}
|
||||
|
||||
fn with_dir<F, T>(dir: &Path, f: F) -> Result<T, String>
|
||||
where
|
||||
F: FnOnce() -> Result<T, String>,
|
||||
{
|
||||
let prev = std::env::current_dir().map_err(|_| "Failed to get current dir".to_string())?;
|
||||
std::env::set_current_dir(dir).map_err(|_| "Failed to change directory".to_string())?;
|
||||
let result = f();
|
||||
let _ = std::env::set_current_dir(prev);
|
||||
result
|
||||
}
|
||||
@@ -7,9 +7,81 @@ int main(void) {
|
||||
}
|
||||
"#;
|
||||
|
||||
pub const FILE_DCR_TEST_H: &str = r#"#pragma once
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#define DCR_TEST_PASS 0
|
||||
#define DCR_TEST_FAIL 1
|
||||
#define DCR_TEST_SKIP 2
|
||||
|
||||
#define EXPECT(expr) \
|
||||
do { \
|
||||
if (!(expr)) { \
|
||||
fprintf(stderr, "assert failed: %s (%s:%d)\n", #expr, __FILE__, __LINE__); \
|
||||
return DCR_TEST_FAIL; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define SKIP(reason) \
|
||||
do { \
|
||||
(void)(reason); \
|
||||
return DCR_TEST_SKIP; \
|
||||
} while (0)
|
||||
|
||||
#define TEST(name) static int name(void)
|
||||
#define TEST_CASE(name) {#name, name}
|
||||
|
||||
typedef int (*test_fn_t)(void);
|
||||
|
||||
typedef struct {
|
||||
const char *name;
|
||||
test_fn_t fn;
|
||||
} test_case_t;
|
||||
|
||||
static inline int dcr_test_raw_mode(int argc, char **argv) {
|
||||
return argc > 1 && strcmp(argv[1], "--dcr-raw") == 0;
|
||||
}
|
||||
|
||||
static inline void dcr_test_print_result(const char *name, int rc, int raw_mode) {
|
||||
const char *tag = (rc == DCR_TEST_PASS) ? "PASS" : (rc == DCR_TEST_SKIP) ? "SKIP" : "FAIL";
|
||||
if (raw_mode) {
|
||||
printf("%s\t%s\n", tag, name);
|
||||
} else {
|
||||
printf("[%s] %s\n", tag, name);
|
||||
}
|
||||
}
|
||||
"#;
|
||||
|
||||
pub const FILE_TEST_C: &str = r#"#include "dcr_test.h"
|
||||
|
||||
TEST(test_example) {
|
||||
EXPECT(1 + 1 == 2);
|
||||
return DCR_TEST_PASS;
|
||||
}
|
||||
|
||||
static test_case_t CASES[] = {
|
||||
TEST_CASE(test_example),
|
||||
};
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
int raw = dcr_test_raw_mode(argc, argv);
|
||||
int rc = 0;
|
||||
int count = (int)(sizeof(CASES) / sizeof(CASES[0]));
|
||||
for (int i = 0; i < count; ++i) {
|
||||
int status = CASES[i].fn();
|
||||
dcr_test_print_result(CASES[i].name, status, raw);
|
||||
if (status == DCR_TEST_FAIL) {
|
||||
rc = 1;
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
"#;
|
||||
|
||||
pub fn flags(profile: &str) -> Option<&'static [&'static str]> {
|
||||
match profile {
|
||||
"release" => Some(&["-O3", "-DNDEBUG", "-march=native"]),
|
||||
"release" => Some(&["-O3", "-DNDEBUG"]),
|
||||
"debug" => Some(&[
|
||||
"-O0",
|
||||
"-g",
|
||||
|
||||
@@ -112,7 +112,7 @@ fn source_extensions(language: &str) -> Vec<&str> {
|
||||
|
||||
fn default_flags(profile: &str) -> &'static [&'static str] {
|
||||
match profile {
|
||||
"release" => &["-O3", "-DNDEBUG", "-march=native"],
|
||||
"release" => &["-O3", "-DNDEBUG"],
|
||||
"debug" => &[
|
||||
"-O0",
|
||||
"-g",
|
||||
@@ -156,9 +156,6 @@ fn build_objects(
|
||||
cmd.arg(format!("-std={}", ctx.standard));
|
||||
}
|
||||
for flag in default_flags(ctx.profile) {
|
||||
if ctx.platform.is_some() && *flag == "-march=native" {
|
||||
continue;
|
||||
}
|
||||
cmd.arg(flag);
|
||||
}
|
||||
for flag in ctx.cflags {
|
||||
|
||||
@@ -20,6 +20,7 @@ fn main() {
|
||||
"init" => cli::init::init(rest),
|
||||
"build" => cli::build::build(rest),
|
||||
"run" => cli::run::run(rest),
|
||||
"test" | "tests" => cli::test::test(rest),
|
||||
"clean" => cli::clean::clean(rest),
|
||||
"gen" => cli::r#gen::r#gen(rest),
|
||||
"--version" => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
use std::sync::Once;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Once;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
static COUNTER: AtomicUsize = AtomicUsize::new(0);
|
||||
@@ -218,3 +218,31 @@ fn workspace_build_and_clean_all() {
|
||||
"root target/debug should remain"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dcr_test_runs_without_sandbox_dependency() {
|
||||
let Some(compiler) = available_compiler() else {
|
||||
eprintln!("no compiler found; skipping dcr test integration");
|
||||
return;
|
||||
};
|
||||
|
||||
let dir = unique_sandbox_dir("dcr_test_independent");
|
||||
let out = run_dcr(&["init"], &dir);
|
||||
assert!(out.status.success(), "dcr init should succeed");
|
||||
|
||||
let envs = [("DCR_CC", compiler)];
|
||||
let out_init = run_dcr_env(&["test", "--init"], &dir, &envs);
|
||||
assert!(out_init.status.success(), "dcr test --init should succeed");
|
||||
|
||||
let out = run_dcr_env(&["test"], &dir, &envs);
|
||||
assert!(out.status.success(), "dcr test should succeed");
|
||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||
assert!(stdout.contains("TOTAL: 1"), "TOTAL summary line missing");
|
||||
assert!(
|
||||
stdout.contains("PASS: 1"),
|
||||
"PASS summary line missing\nstdout:\n{}\nstderr:\n{}",
|
||||
stdout,
|
||||
stderr
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user