Skip to content

Commit 318f290

Browse files
committed
Improve test coverage in new AST code
1 parent 6bbd27d commit 318f290

File tree

3 files changed

+293
-0
lines changed

3 files changed

+293
-0
lines changed
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/*
2+
* Sonar Delphi Plugin
3+
* Copyright (C) 2025 Integrated Application Development
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 3 of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this program; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
18+
*/
19+
package au.com.integradev.delphi.antlr.ast.node;
20+
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
import static org.mockito.Mockito.mock;
23+
24+
import au.com.integradev.delphi.symbol.occurrence.AttributeNameOccurrenceImpl;
25+
import au.com.integradev.delphi.symbol.occurrence.NameOccurrenceImpl;
26+
import au.com.integradev.delphi.utils.files.DelphiFileUtils;
27+
import org.junit.jupiter.api.Test;
28+
import org.junit.jupiter.params.ParameterizedTest;
29+
import org.junit.jupiter.params.provider.ValueSource;
30+
import org.sonar.plugins.communitydelphi.api.ast.AttributeNode;
31+
import org.sonar.plugins.communitydelphi.api.symbol.NameOccurrence;
32+
33+
class AttributeNodeImplTest {
34+
@Test
35+
void testAttribute() {
36+
AttributeNode node = parse("[Foo('Bar')]");
37+
38+
assertThat(node).isNotNull();
39+
assertThat(node.isAssembly()).isFalse();
40+
assertThat(node.getImage()).isEqualTo("Foo('Bar')");
41+
assertThat(node.getNameReference()).isNotNull();
42+
assertThat(node.getArgumentList()).isNotNull();
43+
}
44+
45+
@Test
46+
void testAttributeNameOccurrence() {
47+
AttributeNode node = parse("[Foo]");
48+
49+
assertThat(node.getNameReference()).isNotNull();
50+
assertThat(node.getTypeNameOccurrence()).isNull();
51+
assertThat(node.getConstructorNameOccurrence()).isNull();
52+
53+
var nameReference = (NameReferenceNodeImpl) node.getNameReference();
54+
var constructorNameOccurrence = mock(NameOccurrence.class);
55+
var typeNameOccurrence = new AttributeNameOccurrenceImpl(nameReference);
56+
57+
typeNameOccurrence.setImplicitConstructorNameOccurrence(constructorNameOccurrence);
58+
nameReference.setNameOccurrence(typeNameOccurrence);
59+
60+
assertThat(node.getTypeNameOccurrence()).isEqualTo(typeNameOccurrence);
61+
assertThat(node.getConstructorNameOccurrence()).isEqualTo(constructorNameOccurrence);
62+
}
63+
64+
@Test
65+
void testNonAttributeNameOccurrence() {
66+
AttributeNode node = parse("[Foo]");
67+
68+
assertThat(node.getNameReference()).isNotNull();
69+
assertThat(node.getTypeNameOccurrence()).isNull();
70+
assertThat(node.getConstructorNameOccurrence()).isNull();
71+
72+
var nameReference = (NameReferenceNodeImpl) node.getNameReference();
73+
nameReference.setNameOccurrence(new NameOccurrenceImpl(nameReference));
74+
75+
assertThat(node.getTypeNameOccurrence()).isNull();
76+
assertThat(node.getConstructorNameOccurrence()).isNull();
77+
}
78+
79+
@Test
80+
void testAssemblyAttribute() {
81+
AttributeNode node = parse("[assembly : Foo]");
82+
83+
assertThat(node).isNotNull();
84+
assertThat(node.isAssembly()).isTrue();
85+
assertThat(node.getImage()).isEqualTo("assembly : Foo");
86+
assertThat(node.getNameReference()).isNotNull();
87+
assertThat(node.getArgumentList()).isNull();
88+
}
89+
90+
@ValueSource(
91+
strings = {
92+
"'{B5D90CF6-B2C9-473D-9DB9-1BB75EAFC517}'",
93+
"'{B5D90CF6-B2C9-473D-' + '9DB9-1BB75EAFC517}'"
94+
})
95+
@ParameterizedTest
96+
void testExpressionAttribute(String expression) {
97+
AttributeNode node = parse("[" + expression + "]");
98+
99+
assertThat(node).isNotNull();
100+
assertThat(node.isAssembly()).isFalse();
101+
assertThat(node.getImage()).isEqualTo(expression);
102+
assertThat(node.getNameReference()).isNull();
103+
assertThat(node.getArgumentList()).isNull();
104+
assertThat(node.getTypeNameOccurrence()).isNull();
105+
assertThat(node.getConstructorNameOccurrence()).isNull();
106+
}
107+
108+
private static AttributeNode parse(String attribute) {
109+
return DelphiFileUtils.parse(
110+
"unit Test;",
111+
"",
112+
"interface",
113+
"",
114+
"type",
115+
(" " + attribute),
116+
" TFoo = record",
117+
" end;",
118+
"",
119+
"implementation",
120+
"end.")
121+
.getAst()
122+
.getFirstDescendantOfType(AttributeNode.class);
123+
}
124+
}
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
/*
2+
* Sonar Delphi Plugin
3+
* Copyright (C) 2025 Integrated Application Development
4+
*
5+
* This program is free software; you can redistribute it and/or
6+
* modify it under the terms of the GNU Lesser General Public
7+
* License as published by the Free Software Foundation; either
8+
* version 3 of the License, or (at your option) any later version.
9+
*
10+
* This program is distributed in the hope that it will be useful,
11+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13+
* Lesser General Public License for more details.
14+
*
15+
* You should have received a copy of the GNU Lesser General Public
16+
* License along with this program; if not, write to the Free Software
17+
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
18+
*/
19+
package au.com.integradev.delphi.antlr.ast.node;
20+
21+
import static org.assertj.core.api.Assertions.assertThat;
22+
23+
import au.com.integradev.delphi.utils.files.DelphiFileUtils;
24+
import java.util.stream.Stream;
25+
import org.apache.commons.lang3.StringUtils;
26+
import org.junit.jupiter.api.Named;
27+
import org.junit.jupiter.api.extension.ExtensionContext;
28+
import org.junit.jupiter.params.ParameterizedTest;
29+
import org.junit.jupiter.params.provider.Arguments;
30+
import org.junit.jupiter.params.provider.ArgumentsProvider;
31+
import org.junit.jupiter.params.provider.ArgumentsSource;
32+
import org.sonar.plugins.communitydelphi.api.ast.ExpressionNode;
33+
import org.sonar.plugins.communitydelphi.api.ast.InterfaceTypeNode;
34+
35+
class InterfaceTypeNodeImplTest {
36+
private static final String GUID = "'{B5D90CF6-B2C9-473D-9DB9-1BB75EAFC517}'";
37+
38+
private static class GuidArgumentsProvider implements ArgumentsProvider {
39+
@Override
40+
public Stream<Arguments> provideArguments(ExtensionContext context) {
41+
return Stream.of(
42+
Arguments.of(
43+
Named.of(
44+
"Simple GUID",
45+
parse(
46+
"TFoo = interface", //
47+
" [" + GUID + "]",
48+
"end;"))),
49+
Arguments.of(
50+
Named.of(
51+
"Ambiguous GUID (method attribute?)",
52+
parse(
53+
"TFoo = interface", //
54+
" [" + GUID + "]",
55+
" procedure Bar;",
56+
"end;"))),
57+
Arguments.of(
58+
Named.of(
59+
"Ambiguous GUID (property attribute?)",
60+
parse(
61+
"TFoo = interface", //
62+
" [" + GUID + "]",
63+
" property Bar;",
64+
"end;"))),
65+
Arguments.of(
66+
Named.of(
67+
"GUID within an attribute group",
68+
parse(
69+
"TFoo = interface",
70+
" [SomeAttribute, " + GUID + "]",
71+
" property Bar;",
72+
"end;"))));
73+
}
74+
}
75+
76+
private static class NoGuidArgumentsProvider implements ArgumentsProvider {
77+
@Override
78+
public Stream<Arguments> provideArguments(ExtensionContext context) {
79+
return Stream.of(
80+
Arguments.of(
81+
Named.of(
82+
"Empty interface",
83+
parse(
84+
"TFoo = interface", //
85+
"end;"))),
86+
Arguments.of(
87+
Named.of(
88+
"Attribute declared on the first interface member",
89+
parse(
90+
"TFoo = interface", //
91+
" [SomeAttribute]",
92+
" procedure Bar;",
93+
"end;"))),
94+
Arguments.of(
95+
Named.of(
96+
"String in the second attribute group",
97+
parse(
98+
"TFoo = interface", //
99+
" [SomeAttribute]",
100+
" [" + GUID + "]",
101+
" procedure Bar;",
102+
"end;"))),
103+
Arguments.of(
104+
Named.of(
105+
"String in the first attribute group of the second attribute list",
106+
parse(
107+
"TFoo = interface", //
108+
" [SomeAttribute]",
109+
" procedure Bar;",
110+
" [" + GUID + "]",
111+
" procedure Baz;",
112+
"end;"))));
113+
}
114+
}
115+
116+
@ArgumentsSource(GuidArgumentsProvider.class)
117+
@ParameterizedTest
118+
void testGuidExpressionShouldBeFound(InterfaceTypeNode node) {
119+
ExpressionNode guid = node.getGuidExpression();
120+
assertThat(guid).isNotNull();
121+
assertThat(guid.getImage()).isEqualTo(GUID);
122+
}
123+
124+
@ArgumentsSource(NoGuidArgumentsProvider.class)
125+
@ParameterizedTest
126+
void testGuidExpressionShouldNotBeFound(InterfaceTypeNode node) {
127+
assertThat(node.getGuidExpression()).isNull();
128+
}
129+
130+
@SuppressWarnings("removal")
131+
@ArgumentsSource(GuidArgumentsProvider.class)
132+
@ArgumentsSource(NoGuidArgumentsProvider.class)
133+
@ParameterizedTest
134+
void testGetGuidShouldReturnNull(InterfaceTypeNode node) {
135+
assertThat(node.getGuid()).isNull();
136+
}
137+
138+
private static InterfaceTypeNode parse(String... lines) {
139+
return DelphiFileUtils.parse(
140+
"unit Test;",
141+
"",
142+
"interface",
143+
"",
144+
"type",
145+
" " + StringUtils.join(lines, "\n "),
146+
"",
147+
"implementation",
148+
"end.")
149+
.getAst()
150+
.getFirstDescendantOfType(InterfaceTypeNode.class);
151+
}
152+
}

delphi-frontend/src/test/java/au/com/integradev/delphi/utils/files/DelphiFileUtils.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,35 @@
2323

2424
import au.com.integradev.delphi.DelphiProperties;
2525
import au.com.integradev.delphi.compiler.Platform;
26+
import au.com.integradev.delphi.file.DelphiFile;
2627
import au.com.integradev.delphi.file.DelphiFileConfig;
2728
import au.com.integradev.delphi.preprocessor.DelphiPreprocessorFactory;
2829
import au.com.integradev.delphi.preprocessor.search.SearchPath;
2930
import au.com.integradev.delphi.utils.types.TypeFactoryUtils;
31+
import java.io.IOException;
32+
import java.io.UncheckedIOException;
3033
import java.nio.charset.StandardCharsets;
34+
import java.nio.file.Files;
35+
import java.nio.file.Path;
3136
import java.util.Collections;
37+
import org.apache.commons.lang3.StringUtils;
3238

3339
public final class DelphiFileUtils {
3440
private DelphiFileUtils() {
3541
// Utility class
3642
}
3743

44+
public static DelphiFile parse(String... lines) {
45+
try {
46+
Path path = Files.createTempFile(null, ".pas");
47+
Files.writeString(path, "\uFEFF" + StringUtils.join(lines, '\n'), StandardCharsets.UTF_8);
48+
path.toFile().deleteOnExit();
49+
return DelphiFile.from(path.toFile(), mockConfig());
50+
} catch (IOException e) {
51+
throw new UncheckedIOException(e);
52+
}
53+
}
54+
3855
public static DelphiFileConfig mockConfig() {
3956
DelphiFileConfig mock = mock(DelphiFileConfig.class);
4057
when(mock.getEncoding()).thenReturn(StandardCharsets.UTF_8.name());

0 commit comments

Comments
 (0)