forked from Crockan/MercuryToolbox
chore(release): prepare public source release
This commit is contained in:
@@ -0,0 +1,386 @@
|
||||
//! Integration tests for the `dotnetshape` command.
|
||||
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
|
||||
use assert_cmd::Command;
|
||||
use predicates::prelude::*;
|
||||
use serde_json::Value;
|
||||
use tempfile::TempDir;
|
||||
|
||||
fn cargo_command() -> Command {
|
||||
Command::cargo_bin("dotnetshape").expect("binary")
|
||||
}
|
||||
|
||||
fn write_file(path: &Path, contents: &str) {
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent).expect("parent directory");
|
||||
}
|
||||
fs::write(path, contents).expect("write fixture");
|
||||
}
|
||||
|
||||
fn json_report(root: &Path) -> Value {
|
||||
let output = Command::cargo_bin("dotnetshape")
|
||||
.expect("binary")
|
||||
.arg("--json")
|
||||
.arg(root)
|
||||
.output()
|
||||
.expect("run dotnetshape");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"dotnetshape failed\nstdout:\n{}\nstderr:\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
serde_json::from_slice(&output.stdout).expect("json output")
|
||||
}
|
||||
|
||||
fn fixture_repo() -> TempDir {
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
write_file(
|
||||
&temp.path().join("Directory.Build.props"),
|
||||
r"<Project>
|
||||
<PropertyGroup>
|
||||
<Nullable>enable</Nullable>
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
|
||||
<BaseOutputPath>artifacts/bin/</BaseOutputPath>
|
||||
<AssemblyName>InheritedName</AssemblyName>
|
||||
</PropertyGroup>
|
||||
</Project>",
|
||||
);
|
||||
write_file(
|
||||
&temp.path().join("Directory.Build.targets"),
|
||||
r"<Project>
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
</PropertyGroup>
|
||||
</Project>",
|
||||
);
|
||||
write_file(
|
||||
&temp.path().join("Directory.Packages.props"),
|
||||
r#"<Project>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageVersion Update="xunit" Version="2.9.2" />
|
||||
</ItemGroup>
|
||||
</Project>"#,
|
||||
);
|
||||
write_file(
|
||||
&temp.path().join("repo.sln"),
|
||||
"Microsoft Visual Studio Solution File\n",
|
||||
);
|
||||
write_file(
|
||||
&temp.path().join("src").join("App").join("App.csproj"),
|
||||
r#"<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<AssemblyName>SharedAssembly</AssemblyName>
|
||||
<OutputType>Library</OutputType>
|
||||
<OutputPath>artifacts/shared/</OutputPath>
|
||||
<Nullable Condition="'$(Configuration)' == 'Release'">disable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../Lib/Lib.csproj" />
|
||||
<ProjectReference Include="../Missing/Missing.csproj" />
|
||||
<Reference Include="Legacy">
|
||||
<HintPath>..\lib\Legacy.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="Implicit">
|
||||
<HintPath>..\lib\Implicit.dll</HintPath>
|
||||
</Reference>
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
||||
<PackageReference Include="Missing.Version" />
|
||||
</ItemGroup>
|
||||
<Target Name="ManualCompile">
|
||||
<Csc Sources="Program.cs" />
|
||||
</Target>
|
||||
</Project>"#,
|
||||
);
|
||||
write_file(
|
||||
&temp.path().join("src").join("Lib").join("Lib.csproj"),
|
||||
r#"<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
|
||||
<AssemblyName>SharedAssembly</AssemblyName>
|
||||
<OutputPath>artifacts/shared/</OutputPath>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="PrivateLegacy">
|
||||
<HintPath>..\lib\PrivateLegacy.dll</HintPath>
|
||||
<Private>true</Private>
|
||||
</Reference>
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
|
||||
<PackageReference Include="xunit" />
|
||||
</ItemGroup>
|
||||
</Project>"#,
|
||||
);
|
||||
write_file(
|
||||
&temp
|
||||
.path()
|
||||
.join("tests")
|
||||
.join("Unit.Tests")
|
||||
.join("Unit.Tests.csproj"),
|
||||
r#"<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
<IsTestProject>true</IsTestProject>
|
||||
</PropertyGroup>
|
||||
</Project>"#,
|
||||
);
|
||||
write_file(
|
||||
&temp.path().join("build.ps1"),
|
||||
"Write-Host build\ncsc.exe Program.cs\ndotnet build src/App/App.csproj\n",
|
||||
);
|
||||
temp
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn help_lists_shared_and_repo_options() {
|
||||
let mut command = cargo_command();
|
||||
command
|
||||
.arg("--help")
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("--format <FORMAT>"))
|
||||
.stdout(predicate::str::contains("--json"))
|
||||
.stdout(predicate::str::contains("--toon"))
|
||||
.stdout(predicate::str::contains("--max-depth <COUNT>"))
|
||||
.stdout(predicate::str::contains("--hidden"))
|
||||
.stdout(predicate::str::contains("dotnetshape . --json"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[allow(clippy::too_many_lines)]
|
||||
fn json_reports_static_project_shape_and_diagnostics() {
|
||||
let temp = fixture_repo();
|
||||
let report = json_report(temp.path());
|
||||
|
||||
assert_eq!(report["evaluation_mode"], "static_ancestor_merge");
|
||||
let projects = report["projects"].as_array().expect("projects");
|
||||
assert_eq!(projects.len(), 3);
|
||||
|
||||
let app = projects
|
||||
.iter()
|
||||
.find(|project| project["path"] == "src/App/App.csproj")
|
||||
.expect("app project");
|
||||
assert_eq!(app["sdk"], "Microsoft.NET.Sdk");
|
||||
assert_eq!(app["target_frameworks"], serde_json::json!(["net8.0"]));
|
||||
assert_eq!(app["assembly_name"], "SharedAssembly");
|
||||
assert_eq!(app["output_type"], "Library");
|
||||
assert_eq!(app["nullable"], "enable");
|
||||
assert_eq!(app["treat_warnings_as_errors"], "true");
|
||||
assert_eq!(app["analysis_mode"], "AllEnabledByDefault");
|
||||
assert_eq!(app["kind"], "production");
|
||||
assert!(
|
||||
app["conditioned_properties"]
|
||||
.as_array()
|
||||
.expect("conditioned properties")
|
||||
.iter()
|
||||
.any(|property| property["name"] == "Nullable"
|
||||
&& property["value"] == "disable"
|
||||
&& property["condition"]
|
||||
.as_str()
|
||||
.is_some_and(|condition| condition.contains("Release")))
|
||||
);
|
||||
|
||||
let references = report["project_references"]
|
||||
.as_array()
|
||||
.expect("project refs");
|
||||
assert!(
|
||||
references
|
||||
.iter()
|
||||
.any(|edge| edge["from"] == "src/App/App.csproj"
|
||||
&& edge["to"] == "src/Lib/Lib.csproj"
|
||||
&& edge["resolved"] == true)
|
||||
);
|
||||
assert!(
|
||||
references
|
||||
.iter()
|
||||
.any(|edge| edge["from"] == "src/App/App.csproj"
|
||||
&& edge["to"] == "src/Missing/Missing.csproj"
|
||||
&& edge["resolved"] == false)
|
||||
);
|
||||
|
||||
let hint_refs = report["reference_hints"].as_array().expect("hint refs");
|
||||
assert!(hint_refs.iter().any(|edge| edge["include"] == "Legacy"
|
||||
&& edge["hint_path"] == "../lib/Legacy.dll"
|
||||
&& edge["private"] == false));
|
||||
assert!(
|
||||
hint_refs
|
||||
.iter()
|
||||
.any(|edge| edge["include"] == "Implicit" && edge["private"].is_null())
|
||||
);
|
||||
assert!(
|
||||
hint_refs
|
||||
.iter()
|
||||
.any(|edge| edge["include"] == "PrivateLegacy" && edge["private"] == true)
|
||||
);
|
||||
|
||||
let packages = report["package_references"].as_array().expect("packages");
|
||||
assert!(
|
||||
packages
|
||||
.iter()
|
||||
.any(|package| package["include"] == "Newtonsoft.Json"
|
||||
&& package["version"] == "13.0.3"
|
||||
&& package["version_source"] == "central")
|
||||
);
|
||||
assert!(packages.iter().any(|package| package["include"] == "Dapper"
|
||||
&& package["version"] == "2.1.66"
|
||||
&& package["version_source"] == "inline"));
|
||||
assert!(
|
||||
packages
|
||||
.iter()
|
||||
.any(|package| package["include"] == "Missing.Version"
|
||||
&& package["version"].is_null()
|
||||
&& package["version_source"] == "missing")
|
||||
);
|
||||
assert!(packages.iter().any(|package| package["include"] == "xunit"
|
||||
&& package["version"] == "2.9.2"
|
||||
&& package["version_source"] == "central"));
|
||||
|
||||
assert!(projects.iter().any(|project| {
|
||||
project["path"] == "src/Lib/Lib.csproj"
|
||||
&& project["kind"] == "test"
|
||||
&& project["kind_reasons"]
|
||||
.as_array()
|
||||
.expect("kind reasons")
|
||||
.iter()
|
||||
.any(|reason| {
|
||||
reason
|
||||
.as_str()
|
||||
.is_some_and(|value| value.contains("Microsoft.NET.Test.Sdk"))
|
||||
})
|
||||
}));
|
||||
assert!(projects.iter().any(|project| {
|
||||
project["path"] == "tests/Unit.Tests/Unit.Tests.csproj"
|
||||
&& project["kind"] == "test"
|
||||
&& project["kind_reasons"]
|
||||
.as_array()
|
||||
.expect("kind reasons")
|
||||
.iter()
|
||||
.any(|reason| {
|
||||
reason
|
||||
.as_str()
|
||||
.is_some_and(|value| value.contains("IsTestProject"))
|
||||
})
|
||||
}));
|
||||
|
||||
let diagnostics = report["diagnostics"].as_array().expect("diagnostics");
|
||||
assert!(
|
||||
diagnostics
|
||||
.iter()
|
||||
.any(|diagnostic| diagnostic["kind"] == "duplicate_assembly_name")
|
||||
);
|
||||
assert!(
|
||||
diagnostics
|
||||
.iter()
|
||||
.any(|diagnostic| diagnostic["kind"] == "shared_output_path")
|
||||
);
|
||||
assert!(
|
||||
diagnostics
|
||||
.iter()
|
||||
.any(|diagnostic| diagnostic["kind"] == "unresolved_project_reference")
|
||||
);
|
||||
assert!(
|
||||
diagnostics
|
||||
.iter()
|
||||
.any(|diagnostic| diagnostic["kind"] == "missing_package_version")
|
||||
);
|
||||
|
||||
let bypass_hints = report["build_bypass_hints"]
|
||||
.as_array()
|
||||
.expect("bypass hints");
|
||||
assert!(
|
||||
bypass_hints
|
||||
.iter()
|
||||
.any(|hint| hint["kind"] == "csc_task" && hint["path"] == "src/App/App.csproj")
|
||||
);
|
||||
assert!(
|
||||
bypass_hints.iter().any(|hint| hint["kind"] == "csc_exe"
|
||||
&& hint["path"] == "build.ps1"
|
||||
&& hint["line"] == 2)
|
||||
);
|
||||
assert!(
|
||||
bypass_hints
|
||||
.iter()
|
||||
.any(|hint| hint["kind"] == "direct_project_build"
|
||||
&& hint["path"] == "build.ps1"
|
||||
&& hint["line"] == 3)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn max_depth_and_hidden_control_project_discovery() {
|
||||
let temp = tempfile::tempdir().expect("tempdir");
|
||||
write_file(
|
||||
&temp.path().join("Root.csproj"),
|
||||
r"<Project><PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup></Project>",
|
||||
);
|
||||
write_file(
|
||||
&temp.path().join("deep").join("Nested.csproj"),
|
||||
r"<Project><PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup></Project>",
|
||||
);
|
||||
write_file(
|
||||
&temp.path().join(".hidden").join("Hidden.csproj"),
|
||||
r"<Project><PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup></Project>",
|
||||
);
|
||||
|
||||
let shallow = json_report_with_args(temp.path(), &["--max-depth", "1"]);
|
||||
assert_eq!(shallow["projects"].as_array().expect("projects").len(), 1);
|
||||
assert!(
|
||||
shallow["projects"]
|
||||
.as_array()
|
||||
.expect("projects")
|
||||
.iter()
|
||||
.all(|project| project["path"] != "deep/Nested.csproj"
|
||||
&& project["path"] != ".hidden/Hidden.csproj")
|
||||
);
|
||||
|
||||
let hidden = json_report_with_args(temp.path(), &["--hidden"]);
|
||||
assert!(
|
||||
hidden["projects"]
|
||||
.as_array()
|
||||
.expect("projects")
|
||||
.iter()
|
||||
.any(|project| project["path"] == ".hidden/Hidden.csproj")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_and_toon_outputs_are_structured() {
|
||||
let temp = fixture_repo();
|
||||
let mut text = cargo_command();
|
||||
text.arg(temp.path())
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("dotnetshape"))
|
||||
.stdout(predicate::str::contains("projects:"))
|
||||
.stdout(predicate::str::contains("diagnostics:"));
|
||||
|
||||
let mut toon = cargo_command();
|
||||
toon.arg("--toon")
|
||||
.arg(temp.path())
|
||||
.assert()
|
||||
.success()
|
||||
.stdout(predicate::str::contains("evaluation_mode"))
|
||||
.stdout(predicate::str::contains("projects"));
|
||||
}
|
||||
|
||||
fn json_report_with_args(root: &Path, args: &[&str]) -> Value {
|
||||
let mut command = cargo_command();
|
||||
command.arg("--json");
|
||||
for arg in args {
|
||||
command.arg(arg);
|
||||
}
|
||||
let output = command.arg(root).output().expect("run dotnetshape");
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"dotnetshape failed\nstdout:\n{}\nstderr:\n{}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
serde_json::from_slice(&output.stdout).expect("json output")
|
||||
}
|
||||
Reference in New Issue
Block a user