Skip to content

Commit 0b11d8b

Browse files
LarsEckartJayBazuziisidore
committed
! B KotlinInlineReporter somewhat working
Co-authored-by: Jay Bazuzi <[email protected]> Co-authored-by: Llewellyn Falco <[email protected]>
1 parent 2a3f723 commit 0b11d8b

File tree

2 files changed

+164
-120
lines changed

2 files changed

+164
-120
lines changed

approvaltests-tests/src/test/kotlin/org/approvaltests/inline/KotlinInlineApprovalsTest.kt

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ package org.approvaltests.inline
22

33
import org.approvaltests.Approvals
44
import org.approvaltests.core.Options
5+
import org.approvaltests.inline.InlineKotlinReporter
56
import org.approvaltests.reporters.BeyondCompareReporter
67
import org.junit.jupiter.api.Test
78

89
class KotlinInlineApprovalsTest {
910

1011
@Test
1112
fun testInlineApproval() {
12-
13-
Approvals.verify("hello world", Options().inline("")
13+
val expected = """
14+
hello world
15+
""".trimIndent()
16+
Approvals.verify("hello world", Options().inline(expected)
1417
.withReporter(InlineKotlinReporter(BeyondCompareReporter(), null)))
1518
}
16-
}
19+
}
Lines changed: 158 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -1,135 +1,176 @@
11
package org.approvaltests.inline;
22

3+
import com.spun.util.ArrayUtils;
34
import com.spun.util.StringUtils;
45
import com.spun.util.io.FileUtils;
56
import org.approvaltests.core.ApprovalFailureReporter;
67
import org.approvaltests.core.ApprovalReporterWithCleanUp;
78
import org.approvaltests.namer.StackTraceNamer;
89
import org.lambda.functions.Function2;
910
import org.lambda.functions.Function3;
11+
1012
import java.io.File;
13+
import java.util.regex.Matcher;
14+
import java.util.regex.Pattern;
15+
16+
public class InlineKotlinReporter implements ApprovalFailureReporter, ApprovalReporterWithCleanUp {
17+
private final String sourceFilePath;
18+
private final StackTraceNamer stackTraceNamer;
19+
private final Function2<String, String, String> footerCreator;
20+
private final ApprovalFailureReporter reporter;
21+
private String additionalLines;
22+
private Function3<String, String, String, String> createNewReceivedFileText;
23+
24+
public InlineKotlinReporter(ApprovalFailureReporter reporter) {
25+
this(reporter, null);
26+
}
27+
28+
public InlineKotlinReporter(ApprovalFailureReporter reporter, Function2<String, String, String> footerCreator) {
29+
this.reporter = reporter;
30+
this.stackTraceNamer = new StackTraceNamer();
31+
this.sourceFilePath = stackTraceNamer.getSourceFilePath();
32+
this.createNewReceivedFileText = (kotlinSourceCode, actual, methodName) ->
33+
createNewReceivedFileText(kotlinSourceCode, actual, methodName);
34+
this.footerCreator = footerCreator != null ? footerCreator : (source, actual) -> "";
35+
}
36+
37+
@Override
38+
public boolean report(String received, String approved) {
39+
additionalLines = footerCreator.call(received, approved);
40+
String sourceFile = sourceFilePath + stackTraceNamer.getInfo().getClassName() + ".kt";
41+
String newSource = createReceived(FileUtils.readFile(received));
42+
return reporter.report(newSource, sourceFile);
43+
}
44+
45+
public String createReceived(String actual) {
46+
String file = sourceFilePath + stackTraceNamer.getInfo().getClassName() + ".kt";
47+
String received = getReceivedFileName();
48+
String text = FileUtils.readFile(file);
49+
String fullText = this.createNewReceivedFileText.call(
50+
text,
51+
actual + additionalLines,
52+
this.stackTraceNamer.getInfo().getMethodName()
53+
);
54+
FileUtils.writeFile(new File(received), fullText);
55+
return received;
56+
}
57+
58+
private String getReceivedFileName() {
59+
return sourceFilePath + stackTraceNamer.getInfo().getClassName() + ".received.txt";
60+
}
1161

12-
public class InlineKotlinReporter implements ApprovalFailureReporter, ApprovalReporterWithCleanUp
13-
{
14-
private final String sourceFilePath;
15-
private final StackTraceNamer stackTraceNamer;
16-
private final Function2<String, String, String> footerCreator;
17-
private final ApprovalFailureReporter reporter;
18-
private String additionalLines = null;
19-
private Function3<String, String, String, String> createNewReceivedFileText;
20-
public InlineKotlinReporter(ApprovalFailureReporter reporter, Function2<String, String, String> footerCreator)
21-
{
22-
this.reporter = reporter;
23-
this.stackTraceNamer = new StackTraceNamer();
24-
this.sourceFilePath = stackTraceNamer.getSourceFilePath();
25-
this.createNewReceivedFileText = (kotlinSourceCode, actual,
26-
methodName) -> createNewReceivedFileText(kotlinSourceCode, actual, methodName);
27-
this.footerCreator = footerCreator != null ? footerCreator : (source, actual) -> "";
28-
}
29-
public InlineKotlinReporter(ApprovalFailureReporter reporter)
30-
{
31-
this(reporter, null);
32-
}
33-
@Override
34-
public boolean report(String received, String approved)
35-
{
36-
additionalLines = footerCreator.call(received, approved);
37-
String sourceFile = sourceFilePath + stackTraceNamer.getInfo().getClassName() + ".kt";
38-
String newSource = createReceived(FileUtils.readFile(received));
39-
return reporter.report(newSource, sourceFile);
40-
}
41-
public String createReceived(String actual)
42-
{
43-
String file = sourceFilePath + stackTraceNamer.getInfo().getClassName() + ".kt";
44-
String received = getReceivedFileName();
45-
String text = FileUtils.readFile(file);
46-
String fullText = this.createNewReceivedFileText.call(text, actual + additionalLines,
47-
this.stackTraceNamer.getInfo().getMethodName());
48-
FileUtils.writeFile(new File(received), fullText);
49-
return received;
50-
}
51-
private String getReceivedFileName()
52-
{
53-
return sourceFilePath + stackTraceNamer.getInfo().getClassName() + ".received.txt";
54-
}
55-
@Override
56-
public void cleanUp(String received, String approved)
57-
{
58-
FileUtils.delete(getReceivedFileName());
59-
}
60-
public static String createNewReceivedFileText(String kotlinSourceCode, String actual, String methodName)
61-
{
62-
String normalizedSourceCode = kotlinSourceCode.replaceAll("\\r\\n", "\n");
63-
CodeParts codeParts = CodeParts.splitCode(normalizedSourceCode, methodName);
64-
if (codeParts.method != null && codeParts.method.contains("expected = \"\"\""))
65-
{
66-
replaceExpected(codeParts, actual);
62+
@Override
63+
public void cleanUp(String received, String approved) {
64+
FileUtils.delete(getReceivedFileName());
6765
}
68-
else
69-
{
70-
addExpected(codeParts, actual);
66+
67+
public static String createNewReceivedFileText(String kotlinSourceCode, String actual, String methodName) {
68+
String normalizedSourceCode = kotlinSourceCode.replaceAll("\r\n", "\n");
69+
CodeParts codeParts = splitCode(normalizedSourceCode, methodName);
70+
if (codeParts.method != null && codeParts.method.contains("expected = \"\"\"")) {
71+
replaceExpected(codeParts, actual);
72+
} else {
73+
addExpected(codeParts, actual);
74+
}
75+
return codeParts.getFullCode();
7176
}
72-
return codeParts.getFullCode();
73-
}
74-
private static void addExpected(CodeParts codeParts, String actual)
75-
{
76-
int start = codeParts.method.indexOf("{") + 2;
77-
String before = codeParts.method.substring(0, start);
78-
String after = codeParts.method.substring(start);
79-
codeParts.method = before + getExpected(actual, codeParts.tab) + after;
80-
}
81-
private static String getExpected(String actual, String tab)
82-
{
83-
return String.format("%s%sval expected = \"\"\"\n%s%s%s%s\"\"\".trimIndent()\n", tab, tab, indent(actual, tab),
84-
tab, tab, tab);
85-
}
86-
private static void replaceExpected(CodeParts codeParts, String actual)
87-
{
88-
int start = codeParts.method.indexOf("expected = \"\"\"");
89-
start = codeParts.method.substring(0, start).lastIndexOf("\n") + 1;
90-
// Find the closing """ by looking for it at the start of a line (after whitespace)
91-
int searchPos = start + "expected = \"\"\"".length();
92-
int end = -1;
93-
while (searchPos < codeParts.method.length())
94-
{
95-
int nextTripleQuote = codeParts.method.indexOf("\"\"\"", searchPos);
96-
if (nextTripleQuote == -1)
97-
break;
98-
// Check if this """ is at the start of a line (preceded only by whitespace)
99-
int lineStart = codeParts.method.lastIndexOf("\n", nextTripleQuote - 1) + 1;
100-
String textBeforeQuote = codeParts.method.substring(lineStart, nextTripleQuote);
101-
if (textBeforeQuote.trim().isEmpty())
102-
{
103-
end = nextTripleQuote;
104-
break;
105-
}
106-
searchPos = nextTripleQuote + 1;
77+
78+
private static CodeParts splitCode(String text, String methodName) {
79+
CodeParts codeParts = new CodeParts();
80+
String[] lines = text.split("\n");
81+
// Remove empty trailing elements
82+
int actualLength = lines.length;
83+
while (actualLength > 0 && lines[actualLength - 1].isEmpty()) {
84+
actualLength--;
85+
}
86+
String[] trimmedLines = new String[actualLength];
87+
System.arraycopy(lines, 0, trimmedLines, 0, actualLength);
88+
lines = trimmedLines;
89+
90+
int start = 0;
91+
int end = 0;
92+
for (int i = 0; i < lines.length; i++) {
93+
String line = lines[i];
94+
if (start == 0) {
95+
// Do a regex search to check if a line contains the text "fun"
96+
if (line.matches(".*fun\\s+" + methodName + "\\s*\\(.*")) {
97+
start = i;
98+
codeParts.tab = extractLeadingWhitespace(line);
99+
}
100+
} else if (end == 0) {
101+
if (line.startsWith(codeParts.tab + "}")) {
102+
end = i + 1;
103+
break;
104+
}
105+
}
106+
}
107+
codeParts.before = String.join("\n", ArrayUtils.getSubsection(lines, 0, start));
108+
codeParts.method = String.join("\n", ArrayUtils.getSubsection(lines, start, end));
109+
codeParts.after = String.join("\n", ArrayUtils.getSubsection(lines, end, lines.length));
110+
return codeParts;
107111
}
108-
if (end == -1)
109-
{
110-
// Fallback to old behavior if we can't find it
111-
end = codeParts.method.indexOf("\"\"\"", start + "expected = \"\"\"".length());
112+
113+
private static String extractLeadingWhitespace(String text) {
114+
Pattern pattern = Pattern.compile("^\\s+");
115+
Matcher matcher = pattern.matcher(text);
116+
if (matcher.find()) {
117+
return matcher.group();
118+
}
119+
return "\t";
112120
}
113-
end += 3; // Move past the closing """
114-
// Check if there's a .trimIndent() after the closing """
115-
String afterTripleQuote = codeParts.method.substring(end);
116-
if (afterTripleQuote.startsWith(".trimIndent()"))
117-
{
118-
end += ".trimIndent()".length();
121+
122+
private static void addExpected(CodeParts codeParts, String actual) {
123+
int start = codeParts.method.indexOf("{") + 2;
124+
String before = codeParts.method.substring(0, start);
125+
String after = codeParts.method.substring(start);
126+
codeParts.method = before + getExpected(actual, codeParts.tab) + after;
119127
}
120-
end = codeParts.method.indexOf("\n", end) + 1;
121-
String before = codeParts.method.substring(0, start);
122-
String after = codeParts.method.substring(end);
123-
codeParts.method = before + getExpected(actual, codeParts.tab) + after;
124-
}
125-
public static String indent(String actual, String tab)
126-
{
127-
String[] split = StringUtils.split(actual, "\n");
128-
String output = "";
129-
for (String line : split)
130-
{
131-
output += tab + tab + tab + line + "\n";
128+
129+
private static String getExpected(String actual, String tab) {
130+
return String.format("%s%sval expected = \"\"\"\n%s%s%s%s\"\"\".trimIndent()\n",
131+
tab, tab, indent(actual, tab), tab, tab, tab);
132+
}
133+
134+
private static void replaceExpected(CodeParts codeParts, String actual) {
135+
int start = codeParts.method.indexOf("expected = \"\"\"");
136+
start = codeParts.method.substring(0, start).lastIndexOf("\n") + 1;
137+
// Find the closing """ by looking for it at the start of a line (after whitespace)
138+
int searchPos = start + "expected = \"\"\"".length();
139+
int end = -1;
140+
while (searchPos < codeParts.method.length()) {
141+
int nextTripleQuote = codeParts.method.indexOf("\"\"\"", searchPos);
142+
if (nextTripleQuote == -1) break;
143+
// Check if this """ is at the start of a line (preceded only by whitespace)
144+
int lineStart = codeParts.method.lastIndexOf("\n", nextTripleQuote - 1) + 1;
145+
String textBeforeQuote = codeParts.method.substring(lineStart, nextTripleQuote);
146+
if (textBeforeQuote.trim().isEmpty()) {
147+
end = nextTripleQuote;
148+
break;
149+
}
150+
searchPos = nextTripleQuote + 1;
151+
}
152+
if (end == -1) {
153+
// Fallback to old behavior if we can't find it
154+
end = codeParts.method.indexOf("\"\"\"", start + "expected = \"\"\"".length());
155+
}
156+
end += 3; // Move past the closing """
157+
// Check if there's a .trimIndent() after the closing """
158+
String afterTripleQuote = codeParts.method.substring(end);
159+
if (afterTripleQuote.startsWith(".trimIndent()")) {
160+
end += ".trimIndent()".length();
161+
}
162+
end = codeParts.method.indexOf("\n", end) + 1;
163+
String before = codeParts.method.substring(0, start);
164+
String after = codeParts.method.substring(end);
165+
codeParts.method = before + getExpected(actual, codeParts.tab) + after;
166+
}
167+
168+
private static String indent(String actual, String tab) {
169+
String[] split = StringUtils.split(actual, "\n");
170+
StringBuilder output = new StringBuilder();
171+
for (String line : split) {
172+
output.append(tab).append(tab).append(tab).append(line).append("\n");
173+
}
174+
return output.toString();
132175
}
133-
return output;
134-
}
135176
}

0 commit comments

Comments
 (0)