diff options
author | Rob Mensching <rob@firegiant.com> | 2022-05-09 22:18:19 -0700 |
---|---|---|
committer | Rob Mensching <rob@firegiant.com> | 2022-05-10 13:03:03 -0700 |
commit | 905a6b0c4a214a373cb437ca28ea5610b3ad7654 (patch) | |
tree | 510ee13cb0b0120ea271da848f3f8dbf7ff1ab51 | |
parent | d5744da0117199f23bf72f5c2ba7cd1c6f52e173 (diff) | |
download | wix-905a6b0c4a214a373cb437ca28ea5610b3ad7654.tar.gz wix-905a6b0c4a214a373cb437ca28ea5610b3ad7654.tar.bz2 wix-905a6b0c4a214a373cb437ca28ea5610b3ad7654.zip |
Add WixVersion implementation to WixToolset.Data
-rw-r--r-- | src/api/wix/WixToolset.Data/WixVersion.cs | 275 | ||||
-rw-r--r-- | src/api/wix/WixToolset.Data/WixVersionLabel.cs | 43 | ||||
-rw-r--r-- | src/api/wix/test/WixToolsetTest.Data/WixVerFixture.cs | 204 |
3 files changed, 522 insertions, 0 deletions
diff --git a/src/api/wix/WixToolset.Data/WixVersion.cs b/src/api/wix/WixToolset.Data/WixVersion.cs new file mode 100644 index 00000000..2b02bd7d --- /dev/null +++ b/src/api/wix/WixToolset.Data/WixVersion.cs | |||
@@ -0,0 +1,275 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Data | ||
4 | { | ||
5 | using System; | ||
6 | using System.Collections.Generic; | ||
7 | |||
8 | /// <summary> | ||
9 | /// WiX Toolset's representation of a version designed to support | ||
10 | /// a different version types including 4-part versions with very | ||
11 | /// large numbers and semantic versions with 4-part versions with | ||
12 | /// or without leading "v" indicators. | ||
13 | /// </summary> | ||
14 | [CLSCompliant(false)] | ||
15 | public class WixVersion | ||
16 | { | ||
17 | /// <summary> | ||
18 | /// Gets the prefix of the version if present when parsed. Usually, 'v' or 'V'. | ||
19 | /// </summary> | ||
20 | public char? Prefix { get; set; } | ||
21 | |||
22 | /// <summary> | ||
23 | /// Gets or sets the major version. | ||
24 | /// </summary> | ||
25 | public uint? Major { get; set; } | ||
26 | |||
27 | /// <summary> | ||
28 | /// Gets or sets the minor version. | ||
29 | /// </summary> | ||
30 | public uint? Minor { get; set; } | ||
31 | |||
32 | /// <summary> | ||
33 | /// Gets or sets the patch version. | ||
34 | /// </summary> | ||
35 | public uint? Patch { get; set; } | ||
36 | |||
37 | /// <summary> | ||
38 | /// Gets or sets the revision version. | ||
39 | /// </summary> | ||
40 | public uint? Revision { get; set; } | ||
41 | |||
42 | /// <summary> | ||
43 | /// Gets or sets the labels in the version. | ||
44 | /// </summary> | ||
45 | public WixVersionLabel[] Labels { get; set; } | ||
46 | |||
47 | /// <summary> | ||
48 | /// Gets or sets the metadata in the version. | ||
49 | /// </summary> | ||
50 | public string Metadata { get; set; } | ||
51 | |||
52 | /// <summary> | ||
53 | /// Tries to parse a string value into a valid <c>WixVersion</c>. | ||
54 | /// </summary> | ||
55 | /// <param name="parse">String value to parse into a version.</param> | ||
56 | /// <param name="version">Parsed version.</param> | ||
57 | /// <returns>True if the version was successfully parsed, or false otherwise.</returns> | ||
58 | public static bool TryParse(string parse, out WixVersion version) | ||
59 | { | ||
60 | version = new WixVersion(); | ||
61 | |||
62 | var labels = new List<WixVersionLabel>(); | ||
63 | var start = 0; | ||
64 | var end = parse.Length; | ||
65 | |||
66 | if (end > 0 && (parse[0] == 'v' || parse[0] == 'V')) | ||
67 | { | ||
68 | version.Prefix = parse[0]; | ||
69 | |||
70 | ++start; | ||
71 | } | ||
72 | |||
73 | var partBegin = start; | ||
74 | var partEnd = start; | ||
75 | var lastPart = false; | ||
76 | var trailingDot = false; | ||
77 | var invalid = false; | ||
78 | var currentPart = 0; | ||
79 | var parsedVersionNumber = false; | ||
80 | var expectedReleaseLabels = false; | ||
81 | |||
82 | // Parse version number | ||
83 | while (start < end) | ||
84 | { | ||
85 | trailingDot = false; | ||
86 | |||
87 | // Find end of part. | ||
88 | for (; ; ) | ||
89 | { | ||
90 | if (partEnd >= end) | ||
91 | { | ||
92 | lastPart = true; | ||
93 | break; | ||
94 | } | ||
95 | |||
96 | var ch = parse[partEnd]; | ||
97 | |||
98 | switch (ch) | ||
99 | { | ||
100 | case '0': | ||
101 | case '1': | ||
102 | case '2': | ||
103 | case '3': | ||
104 | case '4': | ||
105 | case '5': | ||
106 | case '6': | ||
107 | case '7': | ||
108 | case '8': | ||
109 | case '9': | ||
110 | ++partEnd; | ||
111 | continue; | ||
112 | case '.': | ||
113 | trailingDot = true; | ||
114 | break; | ||
115 | case '-': | ||
116 | case '+': | ||
117 | lastPart = true; | ||
118 | break; | ||
119 | default: | ||
120 | invalid = true; | ||
121 | break; | ||
122 | } | ||
123 | |||
124 | break; | ||
125 | } | ||
126 | |||
127 | var partLength = partEnd - partBegin; | ||
128 | if (invalid || partLength <= 0) | ||
129 | { | ||
130 | invalid = true; | ||
131 | break; | ||
132 | } | ||
133 | |||
134 | // Parse version part. | ||
135 | var s = parse.Substring(partBegin, partLength); | ||
136 | if (!UInt32.TryParse(s, out var part)) | ||
137 | { | ||
138 | invalid = true; | ||
139 | break; | ||
140 | } | ||
141 | |||
142 | switch (currentPart) | ||
143 | { | ||
144 | case 0: | ||
145 | version.Major = part; | ||
146 | break; | ||
147 | case 1: | ||
148 | version.Minor = part; | ||
149 | break; | ||
150 | case 2: | ||
151 | version.Patch = part; | ||
152 | break; | ||
153 | case 3: | ||
154 | version.Revision = part; | ||
155 | break; | ||
156 | } | ||
157 | |||
158 | if (trailingDot) | ||
159 | { | ||
160 | ++partEnd; | ||
161 | } | ||
162 | partBegin = partEnd; | ||
163 | ++currentPart; | ||
164 | |||
165 | if (4 <= currentPart || lastPart) | ||
166 | { | ||
167 | parsedVersionNumber = true; | ||
168 | break; | ||
169 | } | ||
170 | } | ||
171 | |||
172 | invalid |= !parsedVersionNumber || trailingDot; | ||
173 | |||
174 | if (!invalid && partBegin < end && parse[partBegin] == '-') | ||
175 | { | ||
176 | partBegin = partEnd = partBegin + 1; | ||
177 | expectedReleaseLabels = true; | ||
178 | lastPart = false; | ||
179 | } | ||
180 | |||
181 | while (expectedReleaseLabels && partBegin < end) | ||
182 | { | ||
183 | trailingDot = false; | ||
184 | |||
185 | // Find end of part. | ||
186 | for (; ; ) | ||
187 | { | ||
188 | if (partEnd >= end) | ||
189 | { | ||
190 | lastPart = true; | ||
191 | break; | ||
192 | } | ||
193 | |||
194 | var ch = parse[partEnd]; | ||
195 | if (ch >= '0' && ch <= '9' || | ||
196 | ch >= 'A' && ch <= 'Z' || | ||
197 | ch >= 'a' && ch <= 'z' || | ||
198 | ch == '-') | ||
199 | { | ||
200 | ++partEnd; | ||
201 | continue; | ||
202 | } | ||
203 | else if (ch == '+') | ||
204 | { | ||
205 | lastPart = true; | ||
206 | } | ||
207 | else if (ch == '.') | ||
208 | { | ||
209 | trailingDot = true; | ||
210 | } | ||
211 | else | ||
212 | { | ||
213 | invalid = true; | ||
214 | } | ||
215 | |||
216 | break; | ||
217 | } | ||
218 | |||
219 | var partLength = partEnd - partBegin; | ||
220 | if (invalid || partLength <= 0) | ||
221 | { | ||
222 | invalid = true; | ||
223 | break; | ||
224 | } | ||
225 | |||
226 | WixVersionLabel label; | ||
227 | var partString = parse.Substring(partBegin, partLength); | ||
228 | if (UInt32.TryParse(partString, out var numericPart)) | ||
229 | { | ||
230 | label = new WixVersionLabel(partString, numericPart); | ||
231 | } | ||
232 | else | ||
233 | { | ||
234 | label = new WixVersionLabel(partString); | ||
235 | } | ||
236 | |||
237 | labels.Add(label); | ||
238 | |||
239 | if (trailingDot) | ||
240 | { | ||
241 | ++partEnd; | ||
242 | } | ||
243 | partBegin = partEnd; | ||
244 | |||
245 | if (lastPart) | ||
246 | { | ||
247 | break; | ||
248 | } | ||
249 | } | ||
250 | |||
251 | invalid |= expectedReleaseLabels && (labels.Count == 0 || trailingDot); | ||
252 | |||
253 | if (!invalid && partBegin < end) | ||
254 | { | ||
255 | if (parse[partBegin] == '+') | ||
256 | { | ||
257 | version.Metadata = parse.Substring(partBegin + 1); | ||
258 | } | ||
259 | else | ||
260 | { | ||
261 | invalid = true; | ||
262 | } | ||
263 | } | ||
264 | |||
265 | if (invalid) | ||
266 | { | ||
267 | return false; | ||
268 | } | ||
269 | |||
270 | version.Labels = labels.Count == 0 ? null : labels.ToArray(); | ||
271 | |||
272 | return true; | ||
273 | } | ||
274 | } | ||
275 | } | ||
diff --git a/src/api/wix/WixToolset.Data/WixVersionLabel.cs b/src/api/wix/WixToolset.Data/WixVersionLabel.cs new file mode 100644 index 00000000..c4227c49 --- /dev/null +++ b/src/api/wix/WixToolset.Data/WixVersionLabel.cs | |||
@@ -0,0 +1,43 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolset.Data | ||
4 | { | ||
5 | using System; | ||
6 | |||
7 | /// <summary> | ||
8 | /// Label in a <c>WixVersion</c>. | ||
9 | /// </summary> | ||
10 | [CLSCompliant(false)] | ||
11 | public class WixVersionLabel | ||
12 | { | ||
13 | /// <summary> | ||
14 | /// Creates a string only version label. | ||
15 | /// </summary> | ||
16 | /// <param name="label">String value for version label.</param> | ||
17 | public WixVersionLabel(string label) | ||
18 | { | ||
19 | this.Label = label; | ||
20 | } | ||
21 | |||
22 | /// <summary> | ||
23 | /// Creates a string version label with numeric value. | ||
24 | /// </summary> | ||
25 | /// <param name="label">String value for version label.</param> | ||
26 | /// <param name="numeric">Numeric value for the version label.</param> | ||
27 | public WixVersionLabel(string label, uint? numeric) | ||
28 | { | ||
29 | this.Label = label; | ||
30 | this.Numeric = numeric; | ||
31 | } | ||
32 | |||
33 | /// <summary> | ||
34 | /// Gets the string label value. | ||
35 | /// </summary> | ||
36 | public string Label { get; set; } | ||
37 | |||
38 | /// <summary> | ||
39 | /// Gets the optional numeric label value. | ||
40 | /// </summary> | ||
41 | public uint? Numeric { get; set; } | ||
42 | } | ||
43 | } | ||
diff --git a/src/api/wix/test/WixToolsetTest.Data/WixVerFixture.cs b/src/api/wix/test/WixToolsetTest.Data/WixVerFixture.cs new file mode 100644 index 00000000..45253460 --- /dev/null +++ b/src/api/wix/test/WixToolsetTest.Data/WixVerFixture.cs | |||
@@ -0,0 +1,204 @@ | |||
1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. | ||
2 | |||
3 | namespace WixToolsetTest.Data | ||
4 | { | ||
5 | using System; | ||
6 | using WixToolset.Data; | ||
7 | using Xunit; | ||
8 | |||
9 | public class WixVerFixture | ||
10 | { | ||
11 | [Fact] | ||
12 | public void CannotParseEmptyStringAsVersion() | ||
13 | { | ||
14 | Assert.False(WixVersion.TryParse(String.Empty, out var _)); | ||
15 | } | ||
16 | |||
17 | [Fact] | ||
18 | public void CannotParseInvalidStringAsVersion() | ||
19 | { | ||
20 | Assert.False(WixVersion.TryParse("invalid", out var _)); | ||
21 | } | ||
22 | |||
23 | [Fact] | ||
24 | public void CanParseFourPartVersion() | ||
25 | { | ||
26 | Assert.True(WixVersion.TryParse("1.2.3.4", out var version)); | ||
27 | Assert.Null(version.Prefix); | ||
28 | Assert.Equal((uint)1, version.Major); | ||
29 | Assert.Equal((uint)2, version.Minor); | ||
30 | Assert.Equal((uint)3, version.Patch); | ||
31 | Assert.Equal((uint)4, version.Revision); | ||
32 | Assert.Null(version.Labels); | ||
33 | Assert.Null(version.Metadata); | ||
34 | } | ||
35 | |||
36 | [Fact] | ||
37 | public void CanParseThreePartVersion() | ||
38 | { | ||
39 | Assert.True(WixVersion.TryParse("1.2.3", out var version)); | ||
40 | Assert.Null(version.Prefix); | ||
41 | Assert.Equal((uint)1, version.Major); | ||
42 | Assert.Equal((uint)2, version.Minor); | ||
43 | Assert.Equal((uint)3, version.Patch); | ||
44 | Assert.Null(version.Revision); | ||
45 | Assert.Null(version.Labels); | ||
46 | Assert.Null(version.Metadata); | ||
47 | } | ||
48 | |||
49 | [Fact] | ||
50 | public void CanParseFourPartVersionWithTrailingZero() | ||
51 | { | ||
52 | Assert.True(WixVersion.TryParse("1.2.3.0", out var version)); | ||
53 | Assert.Null(version.Prefix); | ||
54 | Assert.Equal((uint)1, version.Major); | ||
55 | Assert.Equal((uint)2, version.Minor); | ||
56 | Assert.Equal((uint)3, version.Patch); | ||
57 | Assert.Equal((uint)0, version.Revision); | ||
58 | Assert.Null(version.Labels); | ||
59 | Assert.Null(version.Metadata); | ||
60 | } | ||
61 | |||
62 | [Fact] | ||
63 | public void CanParseNumericReleaseLabels() | ||
64 | { | ||
65 | Assert.True(WixVersion.TryParse("1.2-19", out var version)); | ||
66 | Assert.Null(version.Prefix); | ||
67 | Assert.Equal((uint)1, version.Major); | ||
68 | Assert.Equal((uint)2, version.Minor); | ||
69 | Assert.Null(version.Patch); | ||
70 | Assert.Null(version.Revision); | ||
71 | Assert.Equal("19", version.Labels[0].Label); | ||
72 | Assert.Equal((uint)19, version.Labels[0].Numeric); | ||
73 | Assert.Null(version.Metadata); | ||
74 | } | ||
75 | |||
76 | [Fact] | ||
77 | public void CanParseDottedNumericReleaseLabels() | ||
78 | { | ||
79 | Assert.True(WixVersion.TryParse("1.2-2.0", out var version)); | ||
80 | Assert.Null(version.Prefix); | ||
81 | Assert.Equal((uint)1, version.Major); | ||
82 | Assert.Equal((uint)2, version.Minor); | ||
83 | Assert.Null(version.Patch); | ||
84 | Assert.Null(version.Revision); | ||
85 | Assert.Equal("2", version.Labels[0].Label); | ||
86 | Assert.Equal((uint)2, version.Labels[0].Numeric); | ||
87 | Assert.Equal("0", version.Labels[1].Label); | ||
88 | Assert.Equal((uint)0, version.Labels[1].Numeric); | ||
89 | Assert.Null(version.Metadata); | ||
90 | } | ||
91 | |||
92 | [Fact] | ||
93 | public void CanParseHyphenAsVersionSeparator() | ||
94 | { | ||
95 | Assert.True(WixVersion.TryParse("0.0.1-a", out var version)); | ||
96 | Assert.Null(version.Prefix); | ||
97 | Assert.Equal((uint)0, version.Major); | ||
98 | Assert.Equal((uint)0, version.Minor); | ||
99 | Assert.Equal((uint)1, version.Patch); | ||
100 | Assert.Equal("a", version.Labels[0].Label); | ||
101 | Assert.Null(version.Labels[0].Numeric); | ||
102 | Assert.Null(version.Metadata); | ||
103 | } | ||
104 | |||
105 | [Fact] | ||
106 | public void CanParseIgnoringLeadingZeros() | ||
107 | { | ||
108 | Assert.True(WixVersion.TryParse("0.01-a.000", out var version)); | ||
109 | Assert.Null(version.Prefix); | ||
110 | Assert.Equal((uint)0, version.Major); | ||
111 | Assert.Equal((uint)1, version.Minor); | ||
112 | Assert.Null(version.Patch); | ||
113 | Assert.Null(version.Revision); | ||
114 | Assert.Equal("a", version.Labels[0].Label); | ||
115 | Assert.Null(version.Labels[0].Numeric); | ||
116 | Assert.Equal("000", version.Labels[1].Label); | ||
117 | Assert.Equal((uint)0, version.Labels[1].Numeric); | ||
118 | Assert.Null(version.Metadata); | ||
119 | } | ||
120 | |||
121 | [Fact] | ||
122 | public void CanParseMetadata() | ||
123 | { | ||
124 | Assert.True(WixVersion.TryParse("1.2.3+abcd", out var version)); | ||
125 | Assert.Null(version.Prefix); | ||
126 | Assert.Equal((uint)1, version.Major); | ||
127 | Assert.Equal((uint)2, version.Minor); | ||
128 | Assert.Equal((uint)3, version.Patch); | ||
129 | Assert.Null(version.Revision); | ||
130 | Assert.Null(version.Labels); | ||
131 | Assert.Equal("abcd", version.Metadata); | ||
132 | } | ||
133 | |||
134 | [Fact] | ||
135 | public void CannotParseUnexpectedContentAsMetadata() | ||
136 | { | ||
137 | Assert.False(WixVersion.TryParse("1.2.3.abcd", out var _)); | ||
138 | Assert.False(WixVersion.TryParse("1.2.3.-abcd", out var _)); | ||
139 | } | ||
140 | |||
141 | [Fact] | ||
142 | public void CanParseLeadingPrefix() | ||
143 | { | ||
144 | Assert.True(WixVersion.TryParse("v10.20.30.40", out var version)); | ||
145 | Assert.Equal('v', version.Prefix); | ||
146 | Assert.Equal((uint)10, version.Major); | ||
147 | Assert.Equal((uint)20, version.Minor); | ||
148 | Assert.Equal((uint)30, version.Patch); | ||
149 | Assert.Equal((uint)40, version.Revision); | ||
150 | Assert.Null(version.Labels); | ||
151 | Assert.Null(version.Metadata); | ||
152 | |||
153 | Assert.True(WixVersion.TryParse("V100.200.300.400", out var version2)); | ||
154 | Assert.Equal('V', version2.Prefix); | ||
155 | Assert.Equal((uint)100, version2.Major); | ||
156 | Assert.Equal((uint)200, version2.Minor); | ||
157 | Assert.Equal((uint)300, version2.Patch); | ||
158 | Assert.Equal((uint)400, version2.Revision); | ||
159 | Assert.Null(version2.Labels); | ||
160 | Assert.Null(version2.Metadata); | ||
161 | } | ||
162 | |||
163 | [Fact] | ||
164 | public void CanParseVeryLargeNumbers() | ||
165 | { | ||
166 | Assert.True(WixVersion.TryParse("4294967295.4294967295.4294967295.4294967295", out var version)); | ||
167 | Assert.Null(version.Prefix); | ||
168 | Assert.Equal(4294967295, version.Major); | ||
169 | Assert.Equal(4294967295, version.Minor); | ||
170 | Assert.Equal(4294967295, version.Patch); | ||
171 | Assert.Equal(4294967295, version.Revision); | ||
172 | Assert.Null(version.Labels); | ||
173 | Assert.Null(version.Metadata); | ||
174 | } | ||
175 | |||
176 | [Fact] | ||
177 | public void CannotParseTooLargeNumbers() | ||
178 | { | ||
179 | Assert.False(WixVersion.TryParse("4294967296.4294967296.4294967296.4294967296", out var _)); | ||
180 | } | ||
181 | |||
182 | [Fact] | ||
183 | public void CanParseLabelsWithMetadata() | ||
184 | { | ||
185 | Assert.True(WixVersion.TryParse("1.2.3.4-a.b.c.d.5+abc123", out var version)); | ||
186 | Assert.Null(version.Prefix); | ||
187 | Assert.Equal((uint)1, version.Major); | ||
188 | Assert.Equal((uint)2, version.Minor); | ||
189 | Assert.Equal((uint)3, version.Patch); | ||
190 | Assert.Equal((uint)4, version.Revision); | ||
191 | Assert.Equal("a", version.Labels[0].Label); | ||
192 | Assert.Null(version.Labels[0].Numeric); | ||
193 | Assert.Equal("b", version.Labels[1].Label); | ||
194 | Assert.Null(version.Labels[1].Numeric); | ||
195 | Assert.Equal("c", version.Labels[2].Label); | ||
196 | Assert.Null(version.Labels[2].Numeric); | ||
197 | Assert.Equal("d", version.Labels[3].Label); | ||
198 | Assert.Null(version.Labels[3].Numeric); | ||
199 | Assert.Equal("5", version.Labels[4].Label); | ||
200 | Assert.Equal((uint)5, version.Labels[4].Numeric); | ||
201 | Assert.Equal("abc123", version.Metadata); | ||
202 | } | ||
203 | } | ||
204 | } | ||