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
|
/***************************************************************************************************
Copyright (C) 2025 The Qt Company Ltd.
SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
***************************************************************************************************/
using System;
using System.IO;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace Test_Qt.DotNet.Generator
{
using Support;
[TestClass]
public class Test_FnVarNames
{
public TestContext TestContext { get; set; }
private static string ExtractFn(string combined, string baseName)
{
var m = Regex.Match(combined, $@"\bfn{baseName}_[0-9A-F]+\b");
return m.Success ? m.Value : null;
}
[TestMethod]
public async Task FnVarNames()
{
var v1 = new[]
{
"""
public class A
{
public void Alpha() { } // earlier type; stable here
}
""",
"""
public class Foo
{
public void Target(int x) { } // <-- supposed the stay unchanged across versions
}
"""
};
var v2 = new[]
{
"""
public class A
{
public void Alpha() { }
public void NewOne() { } // <-- only change
}
""",
"""
public class Foo
{
public void Target(int x) { } // unchanged, but its number shifts
}
"""
};
var cancelToken = TestContext.CancellationTokenSource.Token;
var r1 = await TestCodeGenerator.GenerateAsync(v1, ct: cancelToken);
Assert.IsTrue(r1.Sink.Files.TryGetValue("source/cpp/foo.cpp", out var r1Cpp));
var r2 = await TestCodeGenerator.GenerateAsync(v2, ct: cancelToken);
Assert.IsTrue(r2.Sink.Files.TryGetValue("source/cpp/foo.cpp", out var r2Cpp));
var id1 = ExtractFn(r1Cpp, "Target");
var id2 = ExtractFn(r2Cpp, "Target");
Assert.IsNotNull(id1, "Expected to find fnTarget_* in v1 output.");
Assert.IsNotNull(id2, "Expected to find fnTarget_* in v2 output.");
Assert.AreEqual(id1, id2, "Unchanged method should produced equal fn name.");
}
private static string BuildToTempFile(AssemblyConfig assemblyConfig,
ReturnConfig returnConfig, ParameterConfig parameterConfig)
{
var dir = Path.Combine(Path.GetTempPath(), "QtDotNetTests", Guid.NewGuid().ToString());
Directory.CreateDirectory(dir);
var fullPath = Path.Combine(dir, assemblyConfig.AssemblyName + ".dll");
using var fs = new FileStream(fullPath, FileMode.Create, FileAccess.ReadWrite,
FileShare.Read);
InMemoryAssemblyBuilder.Build(assemblyConfig, returnConfig, parameterConfig, fs);
fs.Flush(true);
return fullPath;
}
[TestMethod]
public async Task CodeGenerator_CreateUniqueFilesAndfnNames()
{
var pluginAPath = BuildToTempFile(
new AssemblyConfig
{
AssemblyName = "PluginA.Common",
TypeName = "Plugin.Common.Commands",
MethodName = "Run"
},
returnConfig: new ReturnConfig
{
EncodeType = r => r.Void()
},
parameterConfig: new ParameterConfig
{
Names = ["x"],
EncodeTypes = p => p.AddParameter().Type().Int32()
}
);
var pluginBPath = BuildToTempFile(
new AssemblyConfig
{
AssemblyName = "PluginB.Common",
TypeName = "Plugin.Common.Commands",
MethodName = "Run"
},
returnConfig: new ReturnConfig
{
EncodeType = r => r.Void()
},
parameterConfig: new ParameterConfig
{
Names = ["x"],
EncodeTypes = p => p.AddParameter().Type().Int32()
}
);
var pluginDirs = new[]
{
Path.GetDirectoryName(pluginAPath),
Path.GetDirectoryName(pluginBPath)
};
const string source = """
extern alias PluginA;
extern alias PluginB;
namespace Host
{
public static class EntryPoints
{
public static PluginA::Plugin.Common.Commands? _touchA;
public static PluginB::Plugin.Common.Commands? _touchB;
public static void UseA(int x) => PluginA::Plugin.Common.Commands.Run(x);
public static void UseB(int x) => PluginB::Plugin.Common.Commands.Run(x);
}
}
""";
try {
_ = await TestCodeGenerator.GenerateAsync(
sources: [source],
referencesWithAliases:
[
("PluginA", pluginAPath),
("PluginB", pluginBPath)
],
extraRefs: pluginDirs,
ct: TestContext.CancellationTokenSource.Token);
} catch (InvalidOperationException ex) {
Assert.Inconclusive("Debug.Assert triggered (expected temporarily): " + ex.Message);
} finally {
try { Directory.Delete(pluginDirs[0], recursive: true); } catch { /* ignore */ }
try { Directory.Delete(pluginDirs[1], recursive: true); } catch { /* ignore */ }
}
}
}
}
|