1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
|
//! ergebnis/composer-normalize E2E compatibility check: upstream Composer and Shirabe each
//! install the pinned plugin (with its real dependency tree) and the `list`/`help` renderings
//! of its command are compared; the execution comparison is present but ignored until the
//! worker can construct a second native Composer instance.
//!
//! Prerequisites: the PHP runtime, the Composer checkout, and the pinned packages in
//! `fixtures/e2e-normalize/ext/` — run `fixtures/e2e-normalize/fetch` once to populate it.
//! The test skips while any of these is missing; test runs themselves are offline.
use crate::e2e_extension_installer_test::{copy_dir, upstream_composer_bin};
use crate::plugin_installer_test::{lock_php_worker, php_runtime_available};
use std::path::{Path, PathBuf};
use tempfile::TempDir;
fn fixture_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/plugin/fixtures/e2e-normalize")
}
fn fixture_fetched() -> bool {
fixture_dir()
.join("ext/composer-normalize-2.52.0/src/NormalizePlugin.php")
.is_file()
}
struct CommandRun {
exit_code: i32,
stdout: String,
}
fn run_command(work: &Path, program: &str, prefix_args: &[&str], args: &[&str]) -> CommandRun {
let output = std::process::Command::new(program)
.args(prefix_args)
.args(args)
.current_dir(work.join("project"))
.env("COMPOSER_HOME", work.join("home"))
.env("COMPOSER_CACHE_DIR", work.join("cache"))
.env("COMPOSER_NO_INTERACTION", "1")
.env("COLUMNS", "120")
.env("LINES", "30")
.output()
.unwrap();
CommandRun {
exit_code: output.status.code().unwrap_or(-1),
stdout: String::from_utf8_lossy(&output.stdout).into_owned(),
}
}
fn prepared_project(program: &str, prefix: &[&str]) -> TempDir {
let work = TempDir::new().unwrap();
copy_dir(&fixture_dir(), work.path());
let install = run_command(work.path(), program, prefix, &["install"]);
assert_eq!(0, install.exit_code, "{program}: install must succeed");
work
}
fn normalize_lines(stdout: &str) -> Vec<&str> {
stdout
.lines()
.filter(|line| line.contains("normalize"))
.collect()
}
#[test]
fn test_normalize_listing_matches_upstream_composer() {
if !php_runtime_available() {
return;
}
let Some(composer_bin) = upstream_composer_bin() else {
return;
};
if !fixture_fetched() {
return;
}
let _worker = lock_php_worker();
let composer_bin = composer_bin.to_str().unwrap().to_string();
let upstream_prefix = [composer_bin.as_str()];
let u_work = prepared_project("php", &upstream_prefix);
let s_work = prepared_project(env!("CARGO_BIN_EXE_shirabe"), &[]);
let u_list = run_command(
u_work.path(),
"php",
&upstream_prefix,
&["list", "--no-ansi"],
);
let s_list = run_command(
s_work.path(),
env!("CARGO_BIN_EXE_shirabe"),
&[],
&["list", "--no-ansi"],
);
assert_eq!(0, u_list.exit_code);
assert_eq!(u_list.exit_code, s_list.exit_code);
assert_eq!(
normalize_lines(&u_list.stdout),
normalize_lines(&s_list.stdout)
);
assert!(
!normalize_lines(&s_list.stdout).is_empty(),
"list must mention the plugin-provided normalize command"
);
let u_help = run_command(
u_work.path(),
"php",
&upstream_prefix,
&["help", "normalize", "--no-ansi"],
);
let s_help = run_command(
s_work.path(),
env!("CARGO_BIN_EXE_shirabe"),
&[],
&["help", "normalize", "--no-ansi"],
);
assert_eq!(0, u_help.exit_code);
assert_eq!(u_help.exit_code, s_help.exit_code);
assert_eq!(
u_help.stdout, s_help.stdout,
"help normalize output differs"
);
}
// TODO(plugin): NormalizeCommand::execute builds a second, in-process Composer instance
// (`(new Factory())->createComposer(...)`), and the worker's proxy stub classes reject native
// construction of the FQCNs that path instantiates (Composer\Composer, the event dispatcher,
// the package graph, ...); running the command therefore stops at that explicit error.
#[ignore = "the worker cannot construct a second native Composer instance yet; see the TODO(plugin) above"]
#[test]
fn test_normalize_execution_matches_upstream_composer() {
if !php_runtime_available() {
return;
}
let Some(composer_bin) = upstream_composer_bin() else {
return;
};
if !fixture_fetched() {
return;
}
let _worker = lock_php_worker();
let composer_bin = composer_bin.to_str().unwrap().to_string();
let upstream_prefix = [composer_bin.as_str()];
let u_work = prepared_project("php", &upstream_prefix);
let s_work = prepared_project(env!("CARGO_BIN_EXE_shirabe"), &[]);
let dry = ["normalize", "--dry-run", "--no-ansi"];
let u_dry = run_command(u_work.path(), "php", &upstream_prefix, &dry);
let s_dry = run_command(s_work.path(), env!("CARGO_BIN_EXE_shirabe"), &[], &dry);
assert_eq!(1, u_dry.exit_code, "upstream dry-run must report a diff");
assert_eq!(u_dry.exit_code, s_dry.exit_code);
assert_eq!(
u_dry.stdout, s_dry.stdout,
"normalize --dry-run output differs"
);
let real = ["normalize", "--no-update-lock", "--no-ansi"];
let u_run = run_command(u_work.path(), "php", &upstream_prefix, &real);
let s_run = run_command(s_work.path(), env!("CARGO_BIN_EXE_shirabe"), &[], &real);
assert_eq!(0, u_run.exit_code, "upstream normalize must succeed");
assert_eq!(u_run.exit_code, s_run.exit_code);
assert_eq!(
std::fs::read(u_work.path().join("project/composer.json")).unwrap(),
std::fs::read(s_work.path().join("project/composer.json")).unwrap(),
"normalized composer.json differs"
);
}
|