Skip to content
This repository was archived by the owner on Jun 3, 2025. It is now read-only.

Commit d2aa5b3

Browse files
author
mikkomaa
authored
Merge pull request #323 from tmc-cli/annotation-processing-tests-aleksi
Annotation processing tests
2 parents ea30c7e + 45b18a2 commit d2aa5b3

4 files changed

Lines changed: 240 additions & 38 deletions

File tree

HACKING.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,6 @@ public class ExampleCommand extends AbstractCommand {
3131

3232
## Architecture
3333
...
34+
35+
## Debugging
36+
### Logging

src/main/java/fi/helsinki/cs/tmc/cli/command/core/CommandAnnotationProcessor.java

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,38 +30,32 @@ public class CommandAnnotationProcessor extends AbstractProcessor {
3030
private static final Logger logger = LoggerFactory.getLogger(CommandAnnotationProcessor.class);
3131

3232
private static final String CLASS_NAME = "CommandList";
33-
private static final String PACKAGE_NAME = "fi.helsinki.cs.tmc.cli.command";
33+
private static final String PACKAGE_NAME = "fi.helsinki.cs.tmc.cli.command.core";
3434
private static final String TAB = " ";
3535

36-
private ProcessingEnvironment processingEnv;
37-
38-
@Override
39-
public void init(ProcessingEnvironment processingEnv) {
40-
this.processingEnv = processingEnv;
41-
}
42-
4336
private void generateSourceFile(Map<String, String> map) throws IOException {
4437
JavaFileObject jfo = processingEnv.getFiler().createSourceFile(
45-
PACKAGE_NAME + ".core." + CLASS_NAME);
38+
PACKAGE_NAME + "." + CLASS_NAME);
4639

4740
try (Writer writer = jfo.openWriter()) {
4841
BufferedWriter bwriter = new BufferedWriter(writer);
49-
bwriter.append("package ");
50-
bwriter.append(PACKAGE_NAME);
51-
bwriter.append(".core;\n\n");
42+
bwriter.append("package " + PACKAGE_NAME + ";\n\n");
43+
44+
// import the command classes
5245
bwriter.append("//CHECKSTYLE:OFF\n");
53-
bwriter.append("import " + PACKAGE_NAME + ".core.CommandFactory;\n\n");
5446
for (Entry<String, String> entry : map.entrySet()) {
5547
bwriter.append("import " + entry.getValue() + ";\n");
5648
}
5749
bwriter.append("//CHECKSTYLE:ON\n");
50+
5851
bwriter.append("\npublic class " + CLASS_NAME + " {\n");
5952
bwriter.append(TAB + "static {\n");
6053
for (Entry<String, String> entry : map.entrySet()) {
6154
String[] parts = entry.getValue().split("\\.");
6255
if (parts.length == 0) {
6356
continue;
6457
}
58+
// print out the lines that add the commands to the command factory.
6559
String className = parts[parts.length - 1];
6660
bwriter.append(TAB + TAB + "CommandFactory.addCommand(\""
6761
+ entry.getKey() + "\", "
@@ -76,15 +70,15 @@ private void generateSourceFile(Map<String, String> map) throws IOException {
7670
@Override
7771
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
7872
Map<String, String> map = new HashMap<>();
79-
Messager messager = processingEnv.getMessager();
8073

8174
for (Element elem : roundEnv.getElementsAnnotatedWith(Command.class)) {
8275
if (elem.getKind() != ElementKind.CLASS) {
83-
continue;
76+
logger.warn("Element with command annotation is not class: " + elem.toString());
77+
return false;
8478
}
8579
Command command = elem.getAnnotation(Command.class);
86-
messager.printMessage(Diagnostic.Kind.NOTE, elem.toString());
87-
messager.printMessage(Diagnostic.Kind.NOTE, elem.getClass().getCanonicalName());
80+
logger.info("element with annotation: " + elem.toString());
81+
logger.info("element name with annotation: " + elem.getClass().getCanonicalName());
8882

8983
TypeElement classElement = (TypeElement) elem;
9084
map.put(command.name(), processingEnv.getElementUtils()
@@ -94,7 +88,7 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
9488
try {
9589
generateSourceFile(map);
9690
} catch (IOException ex) {
97-
messager.printMessage(Diagnostic.Kind.NOTE, "Failed to create source file." + ex);
91+
logger.warn("Failed to create source file." + ex);
9892
}
9993
return true;
10094
}

src/main/java/fi/helsinki/cs/tmc/cli/io/ResultPrinter.java

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,16 @@
55
import fi.helsinki.cs.tmc.langs.domain.SpecialLogs;
66
import fi.helsinki.cs.tmc.langs.domain.TestResult;
77

8+
import java.util.Arrays;
89
import java.util.List;
910

1011
public class ResultPrinter {
1112

1213
private static final String COMPILE_ERROR_MESSAGE
1314
= Color.colorString("Failed to compile project", Color.AnsiColor.ANSI_PURPLE);
14-
private static final String FAIL = Color.colorString("Failed: ", Color.AnsiColor.ANSI_RED);
15-
private static final String PASS = Color.colorString("Passed: ", Color.AnsiColor.ANSI_GREEN);
16-
private static final String TAB = " ";
17-
private static final char LF = '\n';
15+
private static final String FAIL_MESSAGE = "Failed: ";
16+
private static final String PASS_MESSAGE = "Passed: ";
17+
private final String tab;
1818

1919
private final Io io;
2020

@@ -27,6 +27,8 @@ public ResultPrinter(Io io, boolean showDetails, boolean showPassed) {
2727
this.io = io;
2828
this.showDetails = showDetails;
2929
this.showPassed = showPassed;
30+
31+
this.tab = createPaddingString(PASS_MESSAGE.length());
3032
}
3133

3234
public boolean isShowDetails() {
@@ -155,9 +157,9 @@ public static int passedTests(List<TestResult> testResults) {
155157
private void printTestResults(List<TestResult> testResults) {
156158
for (TestResult testResult : testResults) {
157159
if (!testResult.isSuccessful()) {
158-
io.println(createFailMessage(testResult));
160+
printFailMessage(testResult);
159161
} else if (showPassed) {
160-
io.println(createPassMessage(testResult));
162+
printPassMessage(testResult);
161163
}
162164
}
163165
io.println("Test results: "
@@ -166,37 +168,45 @@ private void printTestResults(List<TestResult> testResults) {
166168

167169
}
168170

169-
private String createFailMessage(TestResult testResult) {
170-
StringBuilder sb = new StringBuilder();
171-
sb.append(FAIL).append(testResult.getName()).append(LF);
172-
sb.append(TAB).append(testResult.getMessage()).append(LF);
171+
private void printFailMessage(TestResult testResult) {
172+
io.print(Color.colorString(FAIL_MESSAGE, Color.AnsiColor.ANSI_RED));
173+
io.println(testResult.getName());
174+
io.println(this.tab + testResult.getMessage());
173175

174176
if (showDetails) {
175-
String details = listToString(testResult.getDetailedMessage(), LF);
177+
String details = listToString(testResult.getDetailedMessage());
176178
if (details != null) {
177-
sb.append(LF).append("Detailed message:").append(LF).append(details);
179+
io.println("\nDetailed message:");
180+
io.println(details);
178181
}
179-
String exception = listToString(testResult.getException(), LF);
182+
183+
String exception = listToString(testResult.getException());
180184
if (exception != null) {
181-
sb.append(LF).append("Exception:").append(LF).append(exception);
185+
io.println("\nException:");
186+
io.println(exception);
182187
}
183188
}
184-
return sb.toString();
185189
}
186190

187-
private String createPassMessage(TestResult testResult) {
188-
return PASS + testResult.getName() + LF;
191+
private void printPassMessage(TestResult testResult) {
192+
io.print(Color.colorString(PASS_MESSAGE, Color.AnsiColor.ANSI_GREEN));
193+
io.println(testResult.getName());
189194
}
190195

191-
private String listToString(List<String> strings, char separator) {
196+
private String listToString(List<String> strings) {
192197
if (strings == null || strings.isEmpty()) {
193198
return null;
194199
}
195200
StringBuilder sb = new StringBuilder();
196201
for (String string : strings) {
197-
sb.append(string).append(separator);
202+
sb.append(string).append("\n");
198203
}
199204
return sb.toString();
200205
}
201206

202-
}
207+
private String createPaddingString(int size) {
208+
char[] charArray = new char[size];
209+
Arrays.fill(charArray, ' ');
210+
return new String(charArray);
211+
}
212+
}
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
package fi.helsinki.cs.tmc.cli.command.core;
2+
3+
import static org.junit.Assert.assertFalse;
4+
import static org.junit.Assert.assertTrue;
5+
import static org.mockito.Matchers.any;
6+
import static org.mockito.Matchers.anyString;
7+
import static org.mockito.Mockito.doReturn;
8+
import static org.mockito.Mockito.mock;
9+
import static org.mockito.Mockito.when;
10+
11+
import fi.helsinki.cs.tmc.cli.io.Io;
12+
13+
import org.apache.commons.cli.CommandLine;
14+
import org.apache.commons.cli.Options;
15+
import org.junit.Test;
16+
17+
import java.io.IOException;
18+
import java.io.StringWriter;
19+
import java.lang.annotation.Annotation;
20+
import java.util.Arrays;
21+
import java.util.HashMap;
22+
import java.util.HashSet;
23+
import java.util.Locale;
24+
import java.util.Map;
25+
import java.util.Set;
26+
import javax.annotation.processing.Filer;
27+
import javax.annotation.processing.Messager;
28+
import javax.annotation.processing.ProcessingEnvironment;
29+
import javax.annotation.processing.RoundEnvironment;
30+
import javax.lang.model.SourceVersion;
31+
import javax.lang.model.element.Element;
32+
import javax.lang.model.element.ElementKind;
33+
import javax.lang.model.element.Name;
34+
import javax.lang.model.element.TypeElement;
35+
import javax.lang.model.util.Elements;
36+
import javax.lang.model.util.Types;
37+
import javax.tools.JavaFileObject;
38+
39+
public class CommandAnnotationProcessorTest {
40+
CommandAnnotationProcessor processor;
41+
RoundEnvironment roundEnv;
42+
StringWriter stringWriter;
43+
Elements mockedElementUtils;
44+
45+
public CommandAnnotationProcessorTest() throws IOException {
46+
final JavaFileObject fileObject = mock(JavaFileObject.class);
47+
final Filer mockedFiler = mock(Filer.class);
48+
when(mockedFiler.createSourceFile(anyString())).thenReturn(fileObject);
49+
50+
stringWriter = new StringWriter();
51+
when(fileObject.openWriter()).thenReturn(stringWriter);
52+
roundEnv = mock(RoundEnvironment.class);
53+
54+
mockedElementUtils = mock(Elements.class);
55+
56+
ProcessingEnvironment processingEnv = new ProcessingEnvironment() {
57+
@Override
58+
public Map<String, String> getOptions() {
59+
return new HashMap<String, String>();
60+
}
61+
62+
@Override
63+
public Messager getMessager() {
64+
return null;
65+
}
66+
67+
@Override
68+
public Filer getFiler() {
69+
return mockedFiler;
70+
}
71+
72+
@Override
73+
public Elements getElementUtils() {
74+
return mockedElementUtils;
75+
}
76+
77+
@Override
78+
public Types getTypeUtils() {
79+
throw new UnsupportedOperationException();
80+
}
81+
82+
@Override
83+
public SourceVersion getSourceVersion() {
84+
throw new UnsupportedOperationException();
85+
}
86+
87+
@Override
88+
public Locale getLocale() {
89+
throw new UnsupportedOperationException();
90+
}
91+
92+
};
93+
processor = new CommandAnnotationProcessor();
94+
processor.init(processingEnv);
95+
}
96+
97+
@Command(name = "commmand1", desc = "abc")
98+
public static class CommandAnnotationExample1 extends AbstractCommand {
99+
@Override
100+
public void getOptions(Options options) {
101+
throw new UnsupportedOperationException();
102+
}
103+
104+
@Override
105+
public void run(CommandLine args, Io io) {
106+
throw new UnsupportedOperationException();
107+
}
108+
}
109+
110+
@Command(name = "commmand2", desc = "abc")
111+
public static class CommandAnnotationExample2 extends AbstractCommand {
112+
@Override
113+
public void getOptions(Options options) {
114+
throw new UnsupportedOperationException();
115+
}
116+
117+
@Override
118+
public void run(CommandLine args, Io io) {
119+
throw new UnsupportedOperationException();
120+
}
121+
}
122+
123+
@Test
124+
public void warnsAboutInvalidElementWithCommandAnnotation() {
125+
final Element classElement = mock(Element.class);
126+
when(classElement.getKind()).thenReturn(ElementKind.FIELD);
127+
doReturn(new HashSet<>(Arrays.asList(classElement))).when(roundEnv)
128+
.getElementsAnnotatedWith(Command.class);
129+
130+
Set<TypeElement> annotations = new HashSet<>();
131+
assertFalse(processor.process(annotations, roundEnv));
132+
}
133+
134+
@Test
135+
public void worksWithoutCommands() {
136+
doReturn(new HashSet<>(Arrays.asList())).when(roundEnv)
137+
.getElementsAnnotatedWith((Class<Annotation>)any(Class.class));
138+
139+
Set<TypeElement> annotations = new HashSet<>();
140+
assertTrue(processor.process(annotations, roundEnv));
141+
assertFalse(stringWriter.toString().contains(".class"));
142+
}
143+
144+
@Test
145+
public void worksWithOneCommands() {
146+
final Element classElement = mock(TypeElement.class);
147+
when(classElement.getKind()).thenReturn(ElementKind.CLASS);
148+
Command annotation = CommandAnnotationExample1.class.getAnnotation(Command.class);
149+
doReturn(annotation).when(classElement).getAnnotation(Command.class);
150+
doReturn(new HashSet<>(Arrays.asList(classElement))).when(roundEnv)
151+
.getElementsAnnotatedWith(any(Class.class));
152+
153+
Name mockedName = mock(Name.class);
154+
when(mockedName.toString())
155+
.thenReturn("abc.TestTest");
156+
when(mockedElementUtils.getBinaryName((TypeElement) classElement))
157+
.thenReturn(mockedName);
158+
159+
Set<TypeElement> annotations = new HashSet<>();
160+
assertTrue(processor.process(annotations, roundEnv));
161+
assertTrue(stringWriter.toString().contains("TestTest.class"));
162+
}
163+
164+
@Test
165+
public void generateCorrectCodeWithTwoCommands() {
166+
final Element classElement1 = mock(TypeElement.class);
167+
final Element classElement2 = mock(TypeElement.class);
168+
when(classElement1.getKind()).thenReturn(ElementKind.CLASS);
169+
when(classElement2.getKind()).thenReturn(ElementKind.CLASS);
170+
Command annotation = CommandAnnotationExample1.class.getAnnotation(Command.class);
171+
doReturn(annotation).when(classElement1).getAnnotation(Command.class);
172+
annotation = CommandAnnotationExample2.class.getAnnotation(Command.class);
173+
doReturn(annotation).when(classElement2).getAnnotation(Command.class);
174+
175+
doReturn(new HashSet<>(Arrays.asList(classElement1, classElement2))).when(roundEnv)
176+
.getElementsAnnotatedWith(any(Class.class));
177+
178+
Name mockedName1 = mock(Name.class);
179+
when(mockedName1.toString())
180+
.thenReturn("abc.TestTest1");
181+
when(mockedElementUtils.getBinaryName((TypeElement) classElement1))
182+
.thenReturn(mockedName1);
183+
184+
Name mockedName2 = mock(Name.class);
185+
when(mockedName2.toString())
186+
.thenReturn("abc.TestTest2");
187+
when(mockedElementUtils.getBinaryName((TypeElement) classElement2))
188+
.thenReturn(mockedName2);
189+
190+
Set<TypeElement> annotations = new HashSet<>();
191+
assertTrue(processor.process(annotations, roundEnv));
192+
assertTrue(stringWriter.toString().contains("(\"commmand1\", TestTest1.class)"));
193+
assertTrue(stringWriter.toString().contains("(\"commmand2\", TestTest2.class)"));
194+
}
195+
}

0 commit comments

Comments
 (0)