Skip to content

Commit 4db76ac

Browse files
paulirwinclaude
andcommitted
Assert on generated output in the integration tests
The four new resources were registered in GeneralSuccessfulConversionTest, which only asserts that conversion returned non-null without warnings. That passes even when the emitted C# is wrong, so it would not catch a regression in any of the fixes it was meant to cover. Three of them are rewritten as example.Program with an `/// - output:` expectation and moved to FullIntegrationTests, which compiles the generated C# with Roslyn, invokes Main, and asserts on captured stdout. Verified by mutation: breaking the `this(...)` chaining guard now fails on the program's actual output. The resources avoid types the harness cannot resolve, since it references only System.Private.CoreLib, System.Console, System.Linq and System.Runtime. Notably java.lang.Math maps to Java.Lang.Math, which does not exist in the BCL, so StaticImports imports from types declared in the file instead. Java8MethodReferences stays conversion-only: java.util.function has no BCL delegate mapping, and C# cannot assign a method group to an interface, so the output cannot be compiled and run. It instead gets explicit assertions on the converted syntax for all four method reference kinds, including that none of them became an invocation. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 parent d0d80e6 commit 4db76ac

6 files changed

Lines changed: 187 additions & 71 deletions

File tree

‎JavaToCSharp.Tests/ConvertJava21GapTests.cs‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,31 @@ public class Shapes {
224224
Assert.Contains("static Shapes()", parsed);
225225
}
226226

227+
/// <summary>
228+
/// Asserts on the converted form of every method reference kind in the integration resource.
229+
/// That resource is conversion-only, since java.util.function has no BCL delegate mapping and
230+
/// so its output cannot be compiled and run by the integration harness.
231+
/// </summary>
232+
[Fact]
233+
public void Method_Reference_Resource_Converts_Every_Reference_Kind()
234+
{
235+
var parsed = Convert(File.ReadAllText("Resources/Java8MethodReferences.java"));
236+
237+
// Instance method of a type, and of a particular object.
238+
Assert.Contains("return string.Length;", parsed);
239+
Assert.Contains("return this.Upper;", parsed);
240+
241+
// Static method.
242+
Assert.Contains("return Java8MethodReferences.Add;", parsed);
243+
244+
// Constructor reference, keeping the generic argument.
245+
Assert.Contains("() => new List<string>()", parsed);
246+
247+
// No method reference may become an invocation.
248+
Assert.DoesNotContain("string.Length()", parsed);
249+
Assert.DoesNotContain("Java8MethodReferences.Add()", parsed);
250+
}
251+
227252
private static string Convert(string javaCode, bool allowWarnings = false)
228253
{
229254
var options = new JavaConversionOptions { IncludeComments = false };

‎JavaToCSharp.Tests/IntegrationTests.cs‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,8 @@ public class IntegrationTests(ITestOutputHelper testOutputHelper)
2222
[InlineData("Resources/Java11LambdaInference.java")]
2323
[InlineData("Resources/MultidimensionalArrays.java", true)]
2424
[InlineData("Resources/Java17SealedClasses.java", true)]
25-
[InlineData("Resources/Java16LocalRecords.java")]
25+
// Conversion-only: java.util.function has no BCL delegate mapping, so the output cannot be run.
2626
[InlineData("Resources/Java8MethodReferences.java")]
27-
[InlineData("Resources/InstanceInitializers.java")]
28-
[InlineData("Resources/StaticImports.java")]
2927
public void GeneralSuccessfulConversionTest(string filePath, bool allowWarnings = false)
3028
{
3129
var options = new JavaConversionOptions
@@ -86,6 +84,9 @@ public void GeneralUnsuccessfulConversionTest(string filePath)
8684
[InlineData("Resources/BooleanArrays.java")]
8785
[InlineData("Resources/BinaryLiterals.java")]
8886
[InlineData("Resources/NestedEnumStaticUsing.java")]
87+
[InlineData("Resources/Java16LocalRecords.java")]
88+
[InlineData("Resources/InstanceInitializers.java")]
89+
[InlineData("Resources/StaticImports.java")]
8990
public void FullIntegrationTests(string filePath, bool allowWarnings = false)
9091
{
9192
var options = new JavaConversionOptions
Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,75 @@
1-
package com.example;
2-
3-
import java.util.ArrayList;
4-
import java.util.List;
5-
6-
/**
7-
* Instance initializer blocks, which Java runs at the start of every constructor that does not
8-
* chain to another constructor of the same class.
9-
*/
10-
public class InstanceInitializers {
11-
private final List<String> names;
12-
private int count;
13-
14-
{
15-
names = new ArrayList<String>();
16-
count = 1;
1+
/// Expect:
2+
/// - output: "a: count=1 tag=init\nb: count=2 tag=x\nc: count=2 tag=x-y\nd: count=1 tag=init\nstatic=7\n"
3+
package example;
4+
5+
// Instance initializer blocks run at the start of every constructor that does not chain to another
6+
// constructor of the same class. A constructor that chains via this(...) must not re-run them.
7+
public class Program {
8+
static int staticValue;
9+
10+
static {
11+
staticValue = 7;
1712
}
1813

19-
public InstanceInitializers() {
14+
public static class Counter {
15+
public String tag;
16+
public int count;
17+
18+
// Two separate initializer blocks, to verify both run and in declaration order.
19+
{
20+
tag = "init";
21+
}
22+
23+
{
24+
count = 1;
25+
}
26+
27+
public Counter() {
28+
}
29+
30+
public Counter(String first) {
31+
tag = first;
32+
count = 2;
33+
}
34+
35+
// Chains to Counter(String), so the initializers already ran there and must not run again.
36+
public Counter(String first, String second) {
37+
this(first);
38+
tag = tag + "-" + second;
39+
}
2040
}
2141

22-
public InstanceInitializers(String first) {
23-
names.add(first);
24-
count = 2;
42+
// A class with an initializer but no declared constructor needs one synthesized, or the
43+
// initializer would be dropped entirely.
44+
public static class Implicit {
45+
public String tag;
46+
public int count;
47+
48+
{
49+
tag = "init";
50+
count = 1;
51+
}
2552
}
2653

27-
public InstanceInitializers(String first, String second) {
28-
this(first);
29-
names.add(second);
54+
public static void describe(String label, int count, String tag) {
55+
System.out.println(label + ": count=" + count + " tag=" + tag);
3056
}
3157

32-
public int getCount() {
33-
return count;
58+
public static void main(String[] args) {
59+
Counter a = new Counter();
60+
describe("a", a.count, a.tag);
61+
62+
Counter b = new Counter("x");
63+
describe("b", b.count, b.tag);
64+
65+
// count stays 2 rather than resetting to 1, because the chained constructor did not re-run
66+
// the initializer.
67+
Counter c = new Counter("x", "y");
68+
describe("c", c.count, c.tag);
69+
70+
Implicit d = new Implicit();
71+
describe("d", d.count, d.tag);
72+
73+
System.out.println("static=" + staticValue);
3474
}
3575
}
Lines changed: 24 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,36 @@
1-
package com.example;
2-
3-
import java.util.ArrayList;
4-
import java.util.List;
5-
6-
/**
7-
* Java 16 local record declarations (JEP 395), which may be declared in a method body.
8-
*/
9-
public class Java16LocalRecords {
10-
public int sumAreas(List<int[]> pairs) {
11-
record Rect(int width, int height) {
12-
}
13-
14-
List<Rect> rects = new ArrayList<Rect>();
15-
16-
for (int[] pair : pairs) {
17-
rects.add(new Rect(pair[0], pair[1]));
1+
/// Expect:
2+
/// - output: "area=6\npair=1,2\nsame=True\nnested=9\n"
3+
package example;
4+
5+
// Java 16 local records (JEP 395) may be declared in a method body. C# has no local record
6+
// declaration, so they are hoisted to the enclosing type.
7+
public class Program {
8+
public static int scale(int value) {
9+
// A local record in a second method, to verify each is hoisted independently.
10+
record Factor(int amount) {
1811
}
1912

20-
int total = 0;
13+
Factor f = new Factor(3);
14+
return value * f.amount;
15+
}
2116

22-
for (Rect rect : rects) {
23-
total += rect.width * rect.height;
17+
public static void main(String[] args) {
18+
record Rect(int width, int height) {
2419
}
2520

26-
return total;
27-
}
21+
Rect r = new Rect(2, 3);
22+
System.out.println("area=" + (r.width * r.height));
2823

29-
public String describe(int a, int b) {
24+
// A second local record in the same method, to verify both are hoisted.
3025
record Pair(int left, int right) {
3126
}
3227

33-
Pair pair = new Pair(a, b);
28+
Pair p = new Pair(1, 2);
29+
System.out.println("pair=" + p.left + "," + p.right);
30+
31+
// Records have value equality in both languages.
32+
System.out.println("same=" + p.equals(new Pair(1, 2)));
3433

35-
return pair.left + "," + pair.right;
34+
System.out.println("nested=" + scale(3));
3635
}
3736
}
Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,51 @@
1-
package com.example;
1+
package example;
22

33
import java.util.ArrayList;
44
import java.util.List;
5+
import java.util.function.BiFunction;
56
import java.util.function.Function;
67
import java.util.function.Supplier;
78

89
/**
9-
* Java 8 method references (JEP 126), which map to C# method groups rather than invocations.
10+
* Java 8 method references (JEP 126).
11+
*
12+
* <p>These convert to C# method groups rather than invocations. This file is a conversion-only
13+
* test: java.util.function has no mapping to BCL delegate types, so the generated C# cannot be
14+
* compiled and run by the integration harness. See ConvertJava21GapTests for the assertions on
15+
* the generated syntax.
1016
*/
1117
public class Java8MethodReferences {
12-
public void printAll(List<String> items) {
13-
items.forEach(System.out::println);
18+
public static int add(int a, int b) {
19+
return a + b;
20+
}
21+
22+
public String value;
23+
24+
public String upper() {
25+
return value.toUpperCase();
1426
}
1527

28+
// Reference to an instance method of a type, applied to a supplied receiver.
1629
public Function<String, Integer> lengthOf() {
1730
return String::length;
1831
}
1932

33+
// Constructor reference, which has no C# equivalent and becomes a lambda.
2034
public Supplier<ArrayList<String>> newList() {
2135
return ArrayList<String>::new;
2236
}
2337

24-
public int count(List<String> items) {
25-
return items.size();
38+
// Reference to a static method.
39+
public BiFunction<Integer, Integer, Integer> adder() {
40+
return Java8MethodReferences::add;
41+
}
42+
43+
// Reference to an instance method of a particular object.
44+
public Supplier<String> upperOf() {
45+
return this::upper;
46+
}
47+
48+
public void printAll(List<String> items) {
49+
items.forEach(System.out::println);
2650
}
2751
}
Lines changed: 39 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,47 @@
1-
package com.example;
1+
/// Expect:
2+
/// - output: "clamped=5\nmax=9\ndoubled=8\nsingle=3\n"
3+
package example;
24

3-
import static java.lang.Math.max;
4-
import static java.lang.Math.min;
5-
import static java.util.Arrays.asList;
5+
import static example.MathHelpers.doubled;
6+
import static example.MathHelpers.max;
7+
import static example.MathHelpers.min;
8+
import static example.Constants.*;
69

7-
import java.util.List;
10+
// A static import names the declaring type, so it must become `using static`. Treating it as a
11+
// namespace import stripped the type off and left the imported members unresolvable.
12+
//
13+
// java.lang.Math is deliberately not used here: it maps to Java.Lang.Math, which does not exist in
14+
// the BCL, so the generated code could not be compiled and run by this harness.
15+
class MathHelpers {
16+
public static int max(int a, int b) {
17+
return a > b ? a : b;
18+
}
19+
20+
public static int min(int a, int b) {
21+
return a < b ? a : b;
22+
}
823

9-
/**
10-
* Static imports, which map to C# <c>using static</c> directives rather than namespace usings.
11-
*/
12-
public class StaticImports {
13-
public int clamp(int value, int low, int high) {
24+
public static int doubled(int a) {
25+
return a * 2;
26+
}
27+
}
28+
29+
class Constants {
30+
public static final int SINGLE = 3;
31+
}
32+
33+
public class Program {
34+
public static int clamp(int value, int low, int high) {
1435
return min(max(value, low), high);
1536
}
1637

17-
public List<String> pair(String a, String b) {
18-
return asList(a, b);
38+
public static void main(String[] args) {
39+
System.out.println("clamped=" + clamp(12, 1, 5));
40+
System.out.println("max=" + max(9, 3));
41+
System.out.println("doubled=" + doubled(4));
42+
43+
// An on-demand static import (`import static example.Constants.*`) already names the type,
44+
// so nothing is stripped from it.
45+
System.out.println("single=" + SINGLE);
1946
}
2047
}

0 commit comments

Comments
 (0)