-
Notifications
You must be signed in to change notification settings - Fork 14
Expand file tree
/
Copy pathtest_linkage_form.cpp
More file actions
319 lines (272 loc) · 13.4 KB
/
Copy pathtest_linkage_form.cpp
File metadata and controls
319 lines (272 loc) · 13.4 KB
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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
// Which form does a dependency take (issue #519)?
//
// The assertions are about the TABLE — its totality, and the identity of each
// cell — rather than about a build looking right afterwards. Two of them cover
// rules that were WRONG in the first draft of the design and were corrected
// against measurements, so they are regression tests for a decision as much as
// for code: the `-L` rule (§4.2) and the libc-linkage coupling (§12.3).
#include <gtest/gtest.h>
import std;
import mcpp.build.linkage_form;
namespace lf = mcpp::build::linkage_form;
namespace {
lf::PackageFacts source_package() {
lf::PackageFacts p;
p.label = "compat.zlib@1.3.2";
p.hasSources = true;
return p;
}
lf::TargetFacts hosted() { return lf::TargetFacts{}; }
lf::Request want(lf::DepLinkage linkage) {
lf::Request r;
r.whole = linkage;
r.wholeIsExplicit = true;
return r;
}
} // namespace
// ── the default is byte-for-byte the old behaviour ─────────────────────────
TEST(LinkageForm, DefaultRequestIsStaticAndSilent) {
lf::Request none; // nobody wrote anything
EXPECT_EQ(none.whole, lf::DepLinkage::Static);
EXPECT_FALSE(none.wholeIsExplicit);
auto pkg = source_package();
auto answer = lf::resolve(pkg, lf::admissible(pkg, hosted()), none);
EXPECT_EQ(answer.linkage, lf::DepLinkage::Static);
EXPECT_TRUE(answer.diagnostic.empty());
}
TEST(LinkageForm, ASourcePackageCanBeEitherForm) {
auto pkg = source_package();
auto allowed = lf::admissible(pkg, hosted());
EXPECT_TRUE(allowed.staticOk);
EXPECT_TRUE(allowed.sharedOk);
EXPECT_EQ(lf::resolve(pkg, allowed, want(lf::DepLinkage::Shared)).linkage,
lf::DepLinkage::Shared);
}
// ── `kind = "shared"` is a constraint; `kind = "lib"` is not ───────────────
TEST(LinkageForm, DeclaredSharedIsAConstraintNotAPreference) {
auto pkg = source_package();
pkg.declaredShared = true;
auto allowed = lf::admissible(pkg, hosted());
EXPECT_FALSE(allowed.staticOk);
EXPECT_TRUE(allowed.sharedOk);
// Asking for static does not get it, and says so.
auto answer = lf::resolve(pkg, allowed, want(lf::DepLinkage::Static));
EXPECT_EQ(answer.linkage, lf::DepLinkage::Shared);
EXPECT_FALSE(answer.diagnostic.empty());
}
TEST(LinkageForm, NotDeclaringSharedIsNotAConstraintToBeStatic) {
// `kind = "lib"` is the parser's DEFAULT, written as boilerplate by 84 of
// the 130 packages in mcpp-index. Reading it as "must be static" would
// lock the whole ecosystem out of this axis, so absence must stay silent.
auto pkg = source_package();
ASSERT_FALSE(pkg.declaredShared);
EXPECT_TRUE(lf::admissible(pkg, hosted()).sharedOk);
}
// ── the `-L` rule (round-two correction) ───────────────────────────────────
TEST(LinkageForm, ForeignLinkInputsAreRecognisedInEverySpelling) {
EXPECT_TRUE(lf::carries_foreign_link_inputs(
std::vector<std::string>{"-Llib", "-l:libssl.a"}));
EXPECT_TRUE(lf::carries_foreign_link_inputs(
std::vector<std::string>{"-L", "lib"}));
EXPECT_TRUE(lf::carries_foreign_link_inputs(
std::vector<std::string>{"-Wl,-Llib"}));
EXPECT_TRUE(lf::carries_foreign_link_inputs(
std::vector<std::string>{"/LIBPATH:lib"}));
}
TEST(LinkageForm, SystemLibrariesAreNotForeignLinkInputs) {
// The 27 mcpp-index packages whose ldflags name only host libraries must
// stay eligible: a shared object depending on libm is ordinary.
EXPECT_FALSE(lf::carries_foreign_link_inputs(
std::vector<std::string>{"-lm", "-lpthread", "-ldl", "-lrt"}));
EXPECT_FALSE(lf::carries_foreign_link_inputs(
std::vector<std::string>{"-lws2_32", "-lbcrypt", "-ladvapi32"}));
}
TEST(LinkageForm, APackageCarryingPrebuiltArchivesCannotBeShared) {
// compat.openssl's shape: one anchor TU that mcpp compiles, plus `.a`
// archives it ships. Wrapping somebody else's non-PIC archive in a shared
// object is not something mcpp can do, so the request is refused WITH a
// reason naming the cause.
auto pkg = source_package();
pkg.label = "compat.openssl@3.5.0";
pkg.carriesForeignLinkInputs = true;
auto allowed = lf::admissible(pkg, hosted());
EXPECT_TRUE(allowed.staticOk);
EXPECT_FALSE(allowed.sharedOk);
EXPECT_NE(allowed.sharedRefusal.find("-L"), std::string::npos);
auto answer = lf::resolve(pkg, allowed, want(lf::DepLinkage::Shared));
EXPECT_EQ(answer.linkage, lf::DepLinkage::Static);
EXPECT_FALSE(answer.diagnostic.empty());
}
TEST(LinkageForm, APackageWithNoSourcesOfItsOwnCannotBeShared) {
lf::PackageFacts pkg;
pkg.label = "vendor.glib@2.80.0";
pkg.hasSources = false;
EXPECT_FALSE(lf::admissible(pkg, hosted()).sharedOk);
}
// ── the target's own veto (round-two correction) ───────────────────────────
TEST(LinkageForm, AFreestandingTargetHasNothingToLoadASharedLibraryWith) {
auto pkg = source_package();
lf::TargetFacts bare;
bare.hasLoader = false;
auto allowed = lf::admissible(pkg, bare);
EXPECT_FALSE(allowed.sharedOk);
EXPECT_EQ(lf::resolve(pkg, allowed, want(lf::DepLinkage::Shared)).linkage,
lf::DepLinkage::Static);
}
TEST(LinkageForm, AFullyStaticLibcVetoesTheSharedForm) {
// The two axes named `linkage` are NOT independent. A `-static` image
// has no interpreter, so it cannot load a shared object — and musl
// defaults to `linkage = "static"`, which makes this the COMMON path
// there rather than a corner.
auto pkg = source_package();
lf::TargetFacts staticLibc;
staticLibc.fullStaticLibc = true;
auto allowed = lf::admissible(pkg, staticLibc);
EXPECT_FALSE(allowed.sharedOk);
EXPECT_NE(allowed.sharedRefusal.find("static"), std::string::npos);
auto answer = lf::resolve(pkg, allowed, want(lf::DepLinkage::Shared));
EXPECT_EQ(answer.linkage, lf::DepLinkage::Static);
// It must SAY why, or the user edits the wrong key forever.
EXPECT_FALSE(answer.diagnostic.empty());
}
TEST(LinkageForm, TheTargetVetoIsReportedBeforeThePackageOne) {
// Order is the order of the diagnostic: a reason the user cannot fix by
// editing the package must not point at the package.
lf::PackageFacts pkg = source_package();
pkg.carriesForeignLinkInputs = true;
lf::TargetFacts bare;
bare.hasLoader = false;
auto allowed = lf::admissible(pkg, bare);
EXPECT_EQ(allowed.sharedRefusal.find("compat.zlib"), std::string::npos);
}
// ── distribution packages answer from what they ship ───────────────────────
TEST(LinkageForm, ADistributionPackageOffersOnlyTheLegsItShips) {
lf::PackageFacts pkg;
pkg.label = "acme.mathkit@0.1.0";
pkg.isDistribution = true;
pkg.shipsStatic = true;
auto allowed = lf::admissible(pkg, hosted());
EXPECT_TRUE(allowed.staticOk);
EXPECT_FALSE(allowed.sharedOk);
EXPECT_NE(allowed.sharedRefusal.find("mathkit"), std::string::npos);
pkg.shipsShared = true;
EXPECT_TRUE(lf::admissible(pkg, hosted()).sharedOk);
}
// ── per-package request ────────────────────────────────────────────────────
TEST(LinkageForm, APerPackageRequestOverridesTheWholeGraphDefault) {
auto pkg = source_package();
lf::Request request; // default static, not explicit
request.perPackage["compat.zlib@1.3.2"] = lf::DepLinkage::Shared;
EXPECT_EQ(lf::resolve(pkg, lf::admissible(pkg, hosted()), request).linkage,
lf::DepLinkage::Shared);
}
TEST(LinkageForm, AnUnaskedForRefusalStaysSilent) {
// mcpp promised nothing when nobody asked. A default that cannot be
// honoured is not a broken promise, and warning about it on every build
// puts noise on correct manifests.
auto pkg = source_package();
pkg.declaredShared = true; // constrained to shared
lf::Request none; // default static, NOT explicit
auto answer = lf::resolve(pkg, lf::admissible(pkg, hosted()), none);
EXPECT_EQ(answer.linkage, lf::DepLinkage::Shared);
EXPECT_TRUE(answer.diagnostic.empty());
}
// ── PIC ────────────────────────────────────────────────────────────────────
TEST(LinkageForm, PicFollowsAnySharedFormInTheGraph) {
using L = lf::DepLinkage;
std::vector<L> allStatic{L::Static, L::Static};
std::vector<L> oneShared{L::Static, L::Shared};
EXPECT_FALSE(lf::needs_pic(allStatic, /*anyOwnSharedTarget=*/false));
EXPECT_TRUE(lf::needs_pic(allStatic, /*anyOwnSharedTarget=*/true));
EXPECT_TRUE(lf::needs_pic(oneShared, /*anyOwnSharedTarget=*/false));
}
// ── vocabulary ─────────────────────────────────────────────────────────────
TEST(LinkageForm, TheVocabularyIsClosed) {
EXPECT_EQ(lf::parse("static"), lf::DepLinkage::Static);
EXPECT_EQ(lf::parse("shared"), lf::DepLinkage::Shared);
EXPECT_FALSE(lf::parse("dynamic").has_value()); // the libc axis' word
EXPECT_FALSE(lf::parse("").has_value());
EXPECT_EQ(lf::to_string(lf::DepLinkage::Static), "static");
EXPECT_EQ(lf::to_string(lf::DepLinkage::Shared), "shared");
}
// ── a package's default form (#642 E1) ─────────────────────────────────────
namespace {
lf::PackageFacts defaulting_to(lf::DepLinkage form) {
auto pkg = source_package();
pkg.defaultLinkage = form;
pkg.defaultDeclaredBy = std::format("[targets.zlib] linkage = \"{}\"",
lf::to_string(form));
return pkg;
}
} // namespace
TEST(LinkageForm, APackageDefaultIsAnsweredWhenNobodyAsks) {
auto pkg = defaulting_to(lf::DepLinkage::Shared);
auto answer = lf::resolve(pkg, lf::admissible(pkg, hosted()), lf::Request{});
EXPECT_EQ(answer.linkage, lf::DepLinkage::Shared);
EXPECT_EQ(answer.reason, "package-default");
EXPECT_TRUE(answer.diagnostic.empty());
EXPECT_TRUE(answer.note.empty());
}
TEST(LinkageForm, APackageDefaultIsNotAConstraint) {
// The difference from `kind = "shared"` is the whole feature: both forms
// stay admissible, so a request for the other one is not refused.
auto pkg = defaulting_to(lf::DepLinkage::Shared);
auto allowed = lf::admissible(pkg, hosted());
EXPECT_TRUE(allowed.staticOk);
EXPECT_TRUE(allowed.sharedOk);
EXPECT_TRUE(allowed.constraint.empty());
}
TEST(LinkageForm, AnEdgeRequestOverridesThePackageDefaultAndNamesBothStatements) {
auto pkg = defaulting_to(lf::DepLinkage::Shared);
lf::Request request;
request.perPackage[pkg.label] = lf::DepLinkage::Static;
auto answer = lf::resolve(pkg, lf::admissible(pkg, hosted()), request);
EXPECT_EQ(answer.linkage, lf::DepLinkage::Static);
EXPECT_EQ(answer.reason, "requested");
// Honoured, so not a degradation: `--strict` must accept it.
EXPECT_TRUE(answer.diagnostic.empty());
EXPECT_NE(answer.note.find("`linkage = \"static\"` on this dependency"),
std::string::npos) << answer.note;
EXPECT_NE(answer.note.find("[targets.zlib] linkage = \"shared\""),
std::string::npos) << answer.note;
}
TEST(LinkageForm, AWrittenWholeGraphValueOutranksThePackageDefault) {
auto pkg = defaulting_to(lf::DepLinkage::Shared);
auto answer = lf::resolve(pkg, lf::admissible(pkg, hosted()),
want(lf::DepLinkage::Static));
EXPECT_EQ(answer.linkage, lf::DepLinkage::Static);
EXPECT_EQ(answer.reason, "requested");
EXPECT_NE(answer.note.find("dependency_linkage = \"static\""), std::string::npos)
<< answer.note;
}
TEST(LinkageForm, AnUnwrittenWholeGraphValueDoesNotOutrankThePackageDefault) {
// `whole` is static by construction; only a human writing it makes it a
// statement, and mcpp's own default is exactly what a package default
// replaces.
auto pkg = defaulting_to(lf::DepLinkage::Shared);
lf::Request none;
ASSERT_EQ(none.whole, lf::DepLinkage::Static);
EXPECT_EQ(lf::resolve(pkg, lf::admissible(pkg, hosted()), none).linkage,
lf::DepLinkage::Shared);
}
TEST(LinkageForm, ARequestThatAgreesWithTheDefaultPrintsNothing) {
auto pkg = defaulting_to(lf::DepLinkage::Shared);
lf::Request request;
request.perPackage[pkg.label] = lf::DepLinkage::Shared;
auto answer = lf::resolve(pkg, lf::admissible(pkg, hosted()), request);
EXPECT_EQ(answer.reason, "requested");
EXPECT_TRUE(answer.note.empty());
}
TEST(LinkageForm, ADefaultTheTargetCannotHonourFallsBackWithoutAWord) {
// Nobody asked, so there is no broken promise to report: the default is
// the package's preference, and a fully static image cannot load a
// shared library at all.
auto pkg = defaulting_to(lf::DepLinkage::Shared);
lf::TargetFacts target;
target.fullStaticLibc = true;
auto answer = lf::resolve(pkg, lf::admissible(pkg, target), lf::Request{});
EXPECT_EQ(answer.linkage, lf::DepLinkage::Static);
EXPECT_EQ(answer.reason, "static-libc");
EXPECT_TRUE(answer.diagnostic.empty());
EXPECT_TRUE(answer.note.empty());
}