Skip to content

Commit 32b8c05

Browse files
committed
Red
1 parent 021189e commit 32b8c05

File tree

2 files changed

+420
-0
lines changed

2 files changed

+420
-0
lines changed
Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
using System.Globalization;
2+
using GitVersion.Core.Tests.Helpers;
3+
using GitVersion.Formatting;
4+
5+
namespace GitVersion.Core.Tests.Formatting;
6+
7+
/// <summary>
8+
/// Tests for backward compatibility with legacy .NET composite format syntax (;;)
9+
/// These tests document the expected behavior before implementing the fix.
10+
/// </summary>
11+
[TestFixture]
12+
public class LegacyFormattingSyntaxTests
13+
{
14+
/// <summary>
15+
/// Test that the old ;;'' syntax for zero-value fallbacks still works
16+
/// This is the exact case from issue #4654
17+
/// </summary>
18+
[Test]
19+
public void FormatWith_LegacyZeroFallbackSyntax_ShouldWork()
20+
{
21+
// Arrange
22+
var semanticVersion = new SemanticVersion
23+
{
24+
Major = 6,
25+
Minor = 13,
26+
Patch = 54,
27+
PreReleaseTag = new SemanticVersionPreReleaseTag("gv6", 1, true),
28+
BuildMetaData = new SemanticVersionBuildMetaData()
29+
{
30+
Branch = "feature/gv6",
31+
VersionSourceSha = "versionSourceSha",
32+
Sha = "489a0c0ab425214def918e36399f3cc3c9a9c42d",
33+
ShortSha = "489a0c0",
34+
CommitsSinceVersionSource = 2,
35+
CommitDate = DateTimeOffset.Parse("2025-08-12", CultureInfo.InvariantCulture)
36+
}
37+
};
38+
39+
// The exact template from the issue
40+
const string template = "{MajorMinorPatch}{PreReleaseLabelWithDash}{CommitsSinceVersionSource:0000;;''}";
41+
const string expected = "6.13.54-gv60002"; // Should format CommitsSinceVersionSource as 0002, not show literal text
42+
43+
// Act
44+
var actual = template.FormatWith(semanticVersion, new TestEnvironment());
45+
46+
// Assert
47+
actual.ShouldBe(expected);
48+
}
49+
50+
/// <summary>
51+
/// Test that legacy positive/negative/zero section syntax works
52+
/// </summary>
53+
[Test]
54+
public void FormatWith_LegacyThreeSectionSyntax_ShouldWork()
55+
{
56+
// Arrange
57+
var testObject = new { Value = -5 };
58+
const string template = "{Value:positive;negative;zero}";
59+
const string expected = "negative";
60+
61+
// Act
62+
var actual = template.FormatWith(testObject, new TestEnvironment());
63+
64+
// Assert
65+
actual.ShouldBe(expected);
66+
}
67+
68+
/// <summary>
69+
/// Test that legacy two-section syntax works (positive;negative)
70+
/// </summary>
71+
[Test]
72+
public void FormatWith_LegacyTwoSectionSyntax_ShouldWork()
73+
{
74+
// Arrange
75+
var testObject = new { Value = -10 };
76+
const string template = "{Value:positive;negative}";
77+
const string expected = "negative";
78+
79+
// Act
80+
var actual = template.FormatWith(testObject, new TestEnvironment());
81+
82+
// Assert
83+
actual.ShouldBe(expected);
84+
}
85+
86+
/// <summary>
87+
/// Test that zero values use the third section in legacy syntax
88+
/// </summary>
89+
[Test]
90+
public void FormatWith_LegacyZeroValue_ShouldUseThirdSection()
91+
{
92+
// Arrange
93+
var testObject = new { Value = 0 };
94+
const string template = "{Value:pos;neg;ZERO}";
95+
const string expected = "ZERO";
96+
97+
// Act
98+
var actual = template.FormatWith(testObject, new TestEnvironment());
99+
100+
// Assert
101+
actual.ShouldBe(expected);
102+
}
103+
104+
/// <summary>
105+
/// Test mixed usage: some properties with legacy syntax, others with new syntax
106+
/// </summary>
107+
[Test]
108+
public void FormatWith_MixedLegacyAndNewSyntax_ShouldWork()
109+
{
110+
// Arrange
111+
var testObject = new
112+
{
113+
OldStyle = 0,
114+
NewStyle = 42,
115+
RegularProp = "test"
116+
};
117+
const string template = "{OldStyle:pos;neg;''}{NewStyle:0000 ?? 'fallback'}{RegularProp}";
118+
const string expected = "0042test"; // Empty string for zero, 0042 for 42, test as-is
119+
120+
// Act
121+
var actual = template.FormatWith(testObject, new TestEnvironment());
122+
123+
// Assert
124+
actual.ShouldBe(expected);
125+
}
126+
127+
/// <summary>
128+
/// Test that complex legacy format with actual .NET format specifiers works
129+
/// </summary>
130+
[Test]
131+
public void FormatWith_LegacyWithStandardFormatSpecifiers_ShouldWork()
132+
{
133+
// Arrange
134+
var testObject = new { Amount = 1234.56 };
135+
const string template = "{Amount:C2;(C2);'No Amount'}";
136+
const string expected = "¤1,234.56"; // Should format as currency
137+
138+
// Act
139+
var actual = template.FormatWith(testObject, new TestEnvironment());
140+
141+
// Assert
142+
actual.ShouldBe(expected);
143+
}
144+
145+
/// <summary>
146+
/// Test that the original failing case from issue #4654 works exactly as expected
147+
/// </summary>
148+
[Test]
149+
public void FormatWith_Issue4654ExactCase_ShouldWork()
150+
{
151+
// Arrange - recreate the exact scenario from the issue
152+
var semanticVersion = new SemanticVersion
153+
{
154+
Major = 6,
155+
Minor = 13,
156+
Patch = 54,
157+
PreReleaseTag = new SemanticVersionPreReleaseTag("gv6", 1, true),
158+
BuildMetaData = new SemanticVersionBuildMetaData("Branch.feature-gv6")
159+
{
160+
CommitsSinceVersionSource = 2
161+
}
162+
};
163+
164+
// This should work on main branch where PreReleaseLabelWithDash would be empty
165+
var mainBranchVersion = new SemanticVersion
166+
{
167+
Major = 6,
168+
Minor = 13,
169+
Patch = 54,
170+
PreReleaseTag = new SemanticVersionPreReleaseTag(string.Empty, 0, true),
171+
BuildMetaData = new SemanticVersionBuildMetaData()
172+
{
173+
CommitsSinceVersionSource = 0
174+
}
175+
};
176+
177+
const string template = "{MajorMinorPatch}{PreReleaseLabelWithDash}{CommitsSinceVersionSource:0000;;''}";
178+
179+
// Act & Assert for feature branch
180+
var featureResult = template.FormatWith(semanticVersion, new TestEnvironment());
181+
featureResult.ShouldBe("6.13.54-gv60002");
182+
183+
// Act & Assert for main branch (zero commits should show empty string)
184+
var mainResult = template.FormatWith(mainBranchVersion, new TestEnvironment());
185+
mainResult.ShouldBe("6.13.54"); // Empty PreReleaseLabelWithDash and empty string for zero commits
186+
}
187+
}
188+
189+
/// <summary>
190+
/// Tests specifically for the regex pattern changes to ensure backward compatibility
191+
/// </summary>
192+
[TestFixture]
193+
public class LegacyRegexPatternTests
194+
{
195+
/// <summary>
196+
/// Test that the ExpandTokensRegex can parse legacy semicolon syntax
197+
/// </summary>
198+
[Test]
199+
public void ExpandTokensRegex_ShouldParseLegacySemicolonSyntax()
200+
{
201+
// Arrange
202+
const string input = "{CommitsSinceVersionSource:0000;;''}";
203+
204+
// Act
205+
var matches = RegexPatterns.Common.ExpandTokensRegex().Matches(input);
206+
207+
// Assert
208+
matches.Count.ShouldBe(1);
209+
var match = matches[0];
210+
match.Groups["member"].Value.ShouldBe("CommitsSinceVersionSource");
211+
212+
// The format group should capture the entire format including semicolons
213+
// This test documents what should happen - the format might need to be "0000;;''"
214+
// or the regex might need to separate format and fallback parts
215+
match.Groups["format"].Success.ShouldBeTrue();
216+
// The exact capture will depend on implementation - this test will guide the regex design
217+
}
218+
219+
/// <summary>
220+
/// Test that both new and old syntax can coexist in the same template
221+
/// </summary>
222+
[Test]
223+
public void ExpandTokensRegex_ShouldHandleMixedSyntax()
224+
{
225+
// Arrange
226+
const string input = "{NewStyle:0000 ?? 'fallback'} {OldStyle:pos;neg;zero}";
227+
228+
// Act
229+
var matches = RegexPatterns.Common.ExpandTokensRegex().Matches(input);
230+
231+
// Assert
232+
matches.Count.ShouldBe(2);
233+
234+
// First match: new syntax
235+
var newMatch = matches[0];
236+
newMatch.Groups["member"].Value.ShouldBe("NewStyle");
237+
newMatch.Groups["fallback"].Value.ShouldBe("fallback");
238+
239+
// Second match: old syntax
240+
var oldMatch = matches[1];
241+
oldMatch.Groups["member"].Value.ShouldBe("OldStyle");
242+
// Format handling for legacy syntax TBD based on implementation approach
243+
}
244+
}

0 commit comments

Comments
 (0)