forked from Crockan/MercuryToolbox
chore(release): prepare public source release
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
[Message: BepInEx] BepInEx 5.4.23.5 - Solar Expanse (2026/4/19 11:22:45)
|
||||
[Info :Solar Expanse Trainer] Pipe client connected (1).
|
||||
[Info :Solar Expanse Trainer] Pipe client connected (2).
|
||||
[Warning: HarmonyX] AccessTools.Method: Could not find method for type Game.UI.Screen.
|
||||
[Error :Solar Expanse Trainer] Pipe server loop failed: System.Threading.ThreadAbortException: Thread was being aborted.
|
||||
at SolarExpanseTrainer.SolarExpanseTrainerPlugin.HandleCommand (SolarExpanseTrainer.PipeCommand command) [0x000b8] in <414166dfdcae444f95d8ea05ca6715f0>:0
|
||||
at SolarExpanseTrainer.SolarExpanseTrainerPlugin.PipeServerMain () [0x0007b] in <414166dfdcae444f95d8ea05ca6715f0>:0
|
||||
[Error : BepInEx] Chainloader startup failed: System.MissingMethodException: Method not found: 'Void Demo.Run(Int32)'.
|
||||
at BepInEx.Bootstrap.Chainloader.Start () [0x0007b] in <414166dfdcae444f95d8ea05ca6715f0>:0
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
Initialize engine version: 2022.3.22f1 (887be4894c44)
|
||||
ArgumentNullException: Value cannot be null.
|
||||
Parameter name: source
|
||||
at Game.AI.ContractHasObjective.OnUpdate () [0x0000d] in <70471b9615aa4ecfa3ed1abb95b73832>:0
|
||||
at UnityEngine.MonoBehaviour:Update()
|
||||
ArgumentNullException: Value cannot be null.
|
||||
Parameter name: source
|
||||
at Game.AI.ContractHasObjective.OnUpdate () [0x0000d] in <70471b9615aa4ecfa3ed1abb95b73832>:0
|
||||
at UnityEngine.MonoBehaviour:Update()
|
||||
The referenced script on this Behaviour (Game Object 'CycleMissionEditWindow') is missing!
|
||||
A formatter has been created for the UnityEngine.Object type Sprite - this is *strongly* discouraged.
|
||||
|
||||
Stacktrace: at Sirenix.Serialization.BaseFormatter`1[T]..cctor () [0x00000] in <c90407e701154d5ea8a975ee4e10d3a4>:0
|
||||
at System.Object.__icall_wrapper_mono_generic_class_init (System.IntPtr) [0x00000] in <27124aa0e30a41659b903b822b959bc7>:0
|
||||
Amplitude Event Sent Successfully
|
||||
Amplitude Event Sent Successfully
|
||||
@@ -0,0 +1,269 @@
|
||||
//! Integration tests for the `unitydiag` command.
|
||||
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
|
||||
use assert_cmd::Command;
|
||||
use filetime::{FileTime, set_file_mtime};
|
||||
use predicates::prelude::*;
|
||||
|
||||
fn cargo_command() -> Command {
|
||||
Command::cargo_bin("unitydiag").expect("binary")
|
||||
}
|
||||
|
||||
fn workspace_root() -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("..")
|
||||
.join("..")
|
||||
.canonicalize()
|
||||
.expect("workspace root")
|
||||
}
|
||||
|
||||
fn fixture(path: &str) -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests")
|
||||
.join("fixtures")
|
||||
.join(path)
|
||||
}
|
||||
|
||||
fn create_temp_dir(label: &str) -> PathBuf {
|
||||
let unique = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("system clock")
|
||||
.as_nanos();
|
||||
let path =
|
||||
std::env::temp_dir().join(format!("unitydiag-{label}-{}-{unique}", std::process::id()));
|
||||
fs::create_dir_all(&path).expect("temp dir");
|
||||
path
|
||||
}
|
||||
|
||||
fn write_fixture(target: &Path, source_name: &str) {
|
||||
let content = fs::read(fixture(source_name)).expect("read fixture");
|
||||
fs::write(target, content).expect("write fixture");
|
||||
}
|
||||
|
||||
fn set_mtime(target: &Path, unix_seconds: i64) {
|
||||
set_file_mtime(target, FileTime::from_unix_time(unix_seconds, 0)).expect("set file mtime");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emits_grouped_text_output_by_incident() {
|
||||
let mut command = cargo_command();
|
||||
command
|
||||
.arg(fixture("player.log"))
|
||||
.arg(fixture("LogOutput.log"))
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("summary logs=2"))
|
||||
.stdout(predicate::str::contains("domain=game"))
|
||||
.stdout(predicate::str::contains("domain=mod"))
|
||||
.stdout(predicate::str::contains("domain=bepinex"))
|
||||
.stdout(predicate::str::contains("count=2"))
|
||||
.stdout(predicate::str::contains(
|
||||
"message=ArgumentNullException: Value cannot be null. | Parameter name: source",
|
||||
))
|
||||
.stdout(predicate::str::contains(
|
||||
"frame=Game.AI.ContractHasObjective.OnUpdate",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn emits_stable_json_object_with_logs_incidents_and_summary() {
|
||||
let mut command = cargo_command();
|
||||
command
|
||||
.arg("--json")
|
||||
.arg("--include-warnings")
|
||||
.arg("--include-info")
|
||||
.arg("--stack")
|
||||
.arg("top")
|
||||
.arg(fixture("player.log"))
|
||||
.arg(fixture("LogOutput.log"))
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("\"logs\":["))
|
||||
.stdout(predicate::str::contains("\"incidents\":["))
|
||||
.stdout(predicate::str::contains("\"summary\":{"))
|
||||
.stdout(predicate::str::contains("\"group_by\":\"incident\""))
|
||||
.stdout(predicate::str::contains("\"domain\":\"telemetry\""))
|
||||
.stdout(predicate::str::contains("\"domain\":\"harmony\""))
|
||||
.stdout(predicate::str::contains("\"kind\":\"player\""))
|
||||
.stdout(predicate::str::contains("\"kind\":\"bepinex\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn supports_powershell_pipeline_input() {
|
||||
let binary = assert_cmd::cargo::cargo_bin("unitydiag");
|
||||
let input = fixture("player.log");
|
||||
let script = format!(
|
||||
"Get-Content '{}' | & '{}' --include-warnings --json",
|
||||
input.display(),
|
||||
binary.display()
|
||||
);
|
||||
|
||||
let mut command = Command::new("pwsh");
|
||||
command
|
||||
.current_dir(workspace_root())
|
||||
.arg("-NoProfile")
|
||||
.arg("-Command")
|
||||
.arg(script)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("\"incidents\":["))
|
||||
.stdout(predicate::str::contains("\"domain\":\"game\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discovers_logs_from_game_root_and_latest() {
|
||||
let root = create_temp_dir("discover");
|
||||
let game_root = root.join("Solar Expanse");
|
||||
let bepinex_dir = game_root.join("BepInEx");
|
||||
let locallow_dir = root
|
||||
.join("AppData")
|
||||
.join("LocalLow")
|
||||
.join("SpaceOps")
|
||||
.join("Solar Expanse");
|
||||
fs::create_dir_all(&bepinex_dir).expect("bepinex dir");
|
||||
fs::create_dir_all(&locallow_dir).expect("locallow dir");
|
||||
|
||||
write_fixture(&locallow_dir.join("Player.log"), "player.log");
|
||||
write_fixture(&bepinex_dir.join("LogOutput.log"), "LogOutput.log");
|
||||
set_mtime(&locallow_dir.join("Player.log"), 1_700_000_000);
|
||||
set_mtime(&bepinex_dir.join("LogOutput.log"), 1_700_000_002);
|
||||
|
||||
let mut command = cargo_command();
|
||||
command
|
||||
.env("USERPROFILE", &root)
|
||||
.arg("--json")
|
||||
.arg("--include-info")
|
||||
.arg("--game-root")
|
||||
.arg(&game_root)
|
||||
.arg("--latest")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("\"logs\":["))
|
||||
.stdout(predicate::str::contains("LogOutput.log"))
|
||||
.stdout(predicate::str::contains("\"log_count\":1"));
|
||||
|
||||
fs::remove_dir_all(&root).expect("cleanup");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn game_root_without_latest_includes_bepinex_and_matching_player_log() {
|
||||
let root = create_temp_dir("discover-both");
|
||||
let game_root = root.join("Solar Expanse");
|
||||
let bepinex_dir = game_root.join("BepInEx");
|
||||
let locallow_dir = root
|
||||
.join("AppData")
|
||||
.join("LocalLow")
|
||||
.join("SpaceOps")
|
||||
.join("Solar Expanse");
|
||||
fs::create_dir_all(&bepinex_dir).expect("bepinex dir");
|
||||
fs::create_dir_all(&locallow_dir).expect("locallow dir");
|
||||
|
||||
write_fixture(&locallow_dir.join("Player.log"), "player.log");
|
||||
write_fixture(&bepinex_dir.join("LogOutput.log"), "LogOutput.log");
|
||||
|
||||
let mut command = cargo_command();
|
||||
command
|
||||
.env("USERPROFILE", &root)
|
||||
.arg("--json")
|
||||
.arg("--include-info")
|
||||
.arg("--game-root")
|
||||
.arg(&game_root)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("\"log_count\":2"))
|
||||
.stdout(predicate::str::contains("LogOutput.log"))
|
||||
.stdout(predicate::str::contains("Player.log"));
|
||||
|
||||
fs::remove_dir_all(&root).expect("cleanup");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discovers_logs_from_relative_dot_game_root() {
|
||||
let root = create_temp_dir("discover-dot");
|
||||
let game_root = root.join("Solar Expanse");
|
||||
let locallow_root = root.join("AppData").join("LocalLow");
|
||||
let target_locallow = locallow_root.join("SpaceOps").join("Solar Expanse");
|
||||
let unrelated_locallow = locallow_root.join("OtherStudio").join("Another Game");
|
||||
fs::create_dir_all(&game_root).expect("game root");
|
||||
fs::create_dir_all(&target_locallow).expect("target locallow");
|
||||
fs::create_dir_all(&unrelated_locallow).expect("unrelated locallow");
|
||||
|
||||
write_fixture(&target_locallow.join("Player.log"), "player.log");
|
||||
write_fixture(&unrelated_locallow.join("Player.log"), "player.log");
|
||||
set_mtime(&target_locallow.join("Player.log"), 1_700_000_000);
|
||||
set_mtime(&unrelated_locallow.join("Player.log"), 1_700_000_002);
|
||||
|
||||
let mut command = cargo_command();
|
||||
command
|
||||
.current_dir(&game_root)
|
||||
.env("USERPROFILE", &root)
|
||||
.arg("--json")
|
||||
.arg("--include-info")
|
||||
.arg("--game-root")
|
||||
.arg(".")
|
||||
.arg("--latest")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("\"logs\":["))
|
||||
.stdout(predicate::str::contains("SpaceOps"))
|
||||
.stdout(predicate::str::contains("Solar Expanse"))
|
||||
.stdout(predicate::str::contains("\"log_count\":1"))
|
||||
.stdout(predicate::str::contains("\"kind\":\"player\""));
|
||||
|
||||
fs::remove_dir_all(&root).expect("cleanup");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_includes_examples_and_grouping_flags() {
|
||||
let mut command = cargo_command();
|
||||
command
|
||||
.arg("--help")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("--group-by"))
|
||||
.stdout(predicate::str::contains("--latest"))
|
||||
.stdout(predicate::str::contains("ConvertFrom-Json"))
|
||||
.stdout(predicate::str::contains(
|
||||
"unitydiag --game-root 'C:\\game' --latest",
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn utf8_bom_log_file_is_parsed_without_losing_incidents() {
|
||||
let root = create_temp_dir("bom");
|
||||
let log_path = root.join("Player.log");
|
||||
let mut bytes = vec![0xEF, 0xBB, 0xBF];
|
||||
bytes.extend(fs::read(fixture("player.log")).expect("fixture"));
|
||||
fs::write(&log_path, bytes).expect("bom fixture");
|
||||
|
||||
let mut command = cargo_command();
|
||||
command
|
||||
.arg("--json")
|
||||
.arg(&log_path)
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("\"log_count\":1"))
|
||||
.stdout(predicate::str::contains("\"domain\":\"game\""));
|
||||
|
||||
fs::remove_dir_all(&root).expect("cleanup");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_utf8_log_file_reports_read_error_instead_of_summarizing() {
|
||||
let root = create_temp_dir("invalid-utf8");
|
||||
let log_path = root.join("Player.log");
|
||||
fs::write(&log_path, b"Initialize engine version\n\xff\xfe\xfd\n").expect("invalid fixture");
|
||||
|
||||
let mut command = cargo_command();
|
||||
command
|
||||
.arg(&log_path)
|
||||
.assert()
|
||||
.failure()
|
||||
.stderr(predicate::str::contains("failed to read"))
|
||||
.stderr(predicate::str::contains("valid UTF-8"));
|
||||
|
||||
fs::remove_dir_all(&root).expect("cleanup");
|
||||
}
|
||||
Reference in New Issue
Block a user