-
Notifications
You must be signed in to change notification settings - Fork 0
/
test_with_package.d
executable file
·231 lines (200 loc) · 5.93 KB
/
test_with_package.d
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
#!/usr/bin/env rdmd
static immutable helpString = q"[Helper script test_with_package.d
Usage:
VERSION=<min/max/both> PACKAGE=<name> rdmd test_with_package.d [-- command...]
VERSION=<min/max/both> rdmd test_with_package.d <package> [-- command...]
rdmd test_with_package.d <min/max/both> <package> [-- command...]
Runs `dub test --compiler=$DC` with either the minimum available version on DUB
or the maximum available version on dub, respecting the dub.json version range.
Running with VERSION=min will use the lowest available version of a package,
for a version specification of format
"~>0.13.0" this will run with "0.13.0",
">=0.13.0" this will run with "0.13.0",
">=0.13.0 <0.15.0" this will run with "0.13.0",
"0.13.0" this will run with "0.13.0"
otherwise error.
Running with VERSION=max will use the highest available version of a package,
for a version specification of format
"~>0.13.0" this will run with "~>0.13.0",
">=0.13.0" this will run with ">=0.13.0",
">=0.13.0 <0.15.0" this will run with "<0.15.0",
"0.13.0" this will run with "0.13.0"
otherwise error.
Running with VERSION=both will run this script first with max, then with min.
This is also the default mode when no VERSION is specified. For CI it may be
better to explicitly state this for parallelism and cleaner test logs though.
To specify a version, either set the VERSION environment variable or pass in the
version (min/max/both) as first argument before the package name.
Running this script either expects a package name as only or as second argument
or if no arguments are given, through the PACKAGE environment variable.
Temporarily creates a dub.json file and renames the original to dub.<n>.json,
both of which is undone automatically on exit.
`dub upgrade` will be run after creating the artificial dub.json and before
running the test command. It might be necessary to call `dub upgrade` manually
again after the test commands are finished to restore the dependencies back to
the newest versions.
If you run with `-- <command>` then that command will be run instead of
`dub test --compiler=$DC`
The script returns 0 on success after all commands or 1 if anything fails. If
`both` is specified as version and both commands fail, this returns 2.
]";
import std;
import fs = std.file;
int main(string[] args)
{
/// wanted target version (min, max or both)
string ver = environment.get("VERSION", "both");
/// package to modify and test
string pkg = environment.get("PACKAGE", "");
/// D compiler to use
const dc = environment.get("DC", "dmd");
auto cmd = ["dub", "test", "--compiler=" ~ dc];
auto cmdIndex = args.countUntil("--");
if (cmdIndex != -1)
{
cmd = args[cmdIndex + 1 .. $];
args = args[0 .. cmdIndex];
}
if (args.any!(among!("-h", "-help", "--help")))
{
stderr.writeln(helpString);
return 0;
}
if (args.length == 2)
{
// <program> <package>
pkg = args[1];
}
else if (args.length == 3)
{
// <program> <version> <package>
ver = args[1];
pkg = args[2];
}
if (!pkg.length)
{
stderr.writefln("No package specified. Try --help?");
return 1;
}
if (ver == "both")
{
int result = 0;
result += doRun("max", pkg, dc, cmd);
stderr.writeln();
result += doRun("min", pkg, dc, cmd);
return result;
}
else
{
return doRun(ver, pkg, dc, cmd);
}
}
int doRun(string ver, string pkg, string dc, string[] cmd)
{
if (!ver.among!("min", "max"))
{
stderr.writefln("Unsupported version '%s', try min, max or both instead",
ver);
return 1;
}
stderr.writefln("(PACKAGE=%s, VERSION=%s, DC=%s)", pkg, ver, dc);
if (!exists("dub.json"))
{
stderr.writefln("No dub.json file exists in the current working "
~ "directory '%s'! dub.sdl files are not supported.", getcwd());
return 1;
}
auto json = parseJSON(readText("dub.json"));
if ("dependencies" !in json || pkg !in json["dependencies"])
{
stderr.writefln("dub.json doesn't specify '%s' as dependency.", pkg);
return 1;
}
auto verSpec = json["dependencies"][pkg];
if (verSpec.type != JSONType.string)
{
stderr.writefln("Unsupported dub.json version '%s' (should be string)",
verSpec);
return 1;
}
// find the version range to use based on the dependency version and wanted
// version target.
string determined = resolveVersion(verSpec.str, ver);
stderr.writefln("Testing using %s version %s.", pkg, determined);
json["dependencies"][pkg] = JSONValue(determined);
// backup dub.json to dub.<n>.json and restore on exit
string tmpDubName;
for (int n = 1;; n++)
{
// lots of GC alloc but it doesn't matter for a script like this
tmpDubName = "dub." ~ n.to!string ~ ".json";
if (!exists(tmpDubName))
break;
}
fs.rename("dub.json", tmpDubName);
scope (exit)
fs.rename(tmpDubName, "dub.json");
// create dummy dub.json and delete on exit
fs.write("dub.json", json.toPrettyString);
scope (exit)
fs.remove("dub.json");
stderr.writeln("$ dub upgrade");
if (spawnShell("dub upgrade").wait != 0)
return 1;
stderr.writefln("$ %(%s %)", cmd);
if (spawnProcess(cmd).wait != 0)
return 1;
return 0;
}
string resolveVersion(string verRange, string wanted)
{
import std.ascii : isDigit;
if (verRange.startsWith("~>"))
{
switch (wanted)
{
case "min":
return verRange[2 .. $];
case "max":
return verRange;
default:
assert(false, "unknown target version " ~ wanted);
}
}
else if (verRange.startsWith(">="))
{
auto end = verRange.indexOf("<");
if (end == -1)
{
switch (wanted)
{
case "min":
return verRange[2 .. $];
case "max":
return verRange;
default:
assert(false, "unknown target version " ~ wanted);
}
}
else
{
switch (wanted)
{
case "min":
return verRange[2 .. end].strip;
case "max":
return verRange[end .. $];
default:
assert(false, "unknown target version " ~ wanted);
}
}
}
else if (verRange.length && verRange[0].isDigit)
{
// exact range
return verRange;
}
else
throw new Exception("Unsupported version range specifier to multi-test: "
~ verRange);
}