1 /*
2 * *************************************************************************************************************************************************************
3 *
4 * TheseFoolishThings: Miscellaneous utilities
5 * http://tidalwave.it/projects/thesefoolishthings
6 *
7 * Copyright (C) 2009 - 2025 by Tidalwave s.a.s. (http://tidalwave.it)
8 *
9 * *************************************************************************************************************************************************************
10 *
11 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
13 *
14 * http://www.apache.org/licenses/LICENSE-2.0
15 *
16 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
17 * CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
18 *
19 * *************************************************************************************************************************************************************
20 *
21 * git clone https://bitbucket.org/tidalwave/thesefoolishthings-src
22 * git clone https://github.com/tidalwave-it/thesefoolishthings-src
23 *
24 * *************************************************************************************************************************************************************
25 */
26 package it.tidalwave.util.test;
27
28 import jakarta.annotation.Nonnull;
29 import java.util.List;
30 import java.io.FileNotFoundException;
31 import java.io.IOException;
32 import java.nio.file.Files;
33 import java.nio.file.Path;
34 import java.nio.file.Paths;
35 import lombok.RequiredArgsConstructor;
36 import lombok.extern.slf4j.Slf4j;
37 import static java.nio.charset.StandardCharsets.UTF_8;
38 import static it.tidalwave.util.test.FileComparisonUtils.assertSameContents;
39 import static lombok.AccessLevel.PRIVATE;
40
41 /***************************************************************************************************************************************************************
42 *
43 * A facility that provides some common tasks for testing, such as manipulating test files.
44 *
45 * @author Fabrizio Giudici
46 * @since 3.2-ALPHA-18
47 *
48 **************************************************************************************************************************************************************/
49 @RequiredArgsConstructor @Slf4j
50 public class BaseTestHelper
51 {
52 @Nonnull
53 protected final Object test;
54
55 /***********************************************************************************************************************************************************
56 * Returns a {@link Path} for a resource file. The resource should be placed under
57 * {@code src/test/resources/test-class-simple-name/test-resources/resource-name}. Note that the file actually
58 * loaded is the one under {@code target/test-classes} copied there (and eventually filtered) by Maven.
59 *
60 * @param resourceName the resource name
61 * @return the {@code Path}
62 **********************************************************************************************************************************************************/
63 @Nonnull
64 public Path resourceFileFor (@Nonnull final String resourceName)
65 {
66 final var testName = test.getClass().getSimpleName();
67 return Paths.get("target/test-classes", testName, "test-resources", resourceName);
68 }
69
70 /***********************************************************************************************************************************************************
71 * Reads the content from the resource file as a single string. See {@link #resourceFileFor(String)} for
72 * further info.
73 *
74 * @param resourceName the resource name
75 * @return the string
76 * @throws IOException in case of error
77 **********************************************************************************************************************************************************/
78 @Nonnull
79 public String readStringFromResource (@Nonnull final String resourceName)
80 throws IOException
81 {
82 final var file = resourceFileFor(resourceName);
83 final var buffer = new StringBuilder();
84 var separator = "";
85
86 for (final var string : Files.readAllLines(file, UTF_8))
87 {
88 buffer.append(separator).append(string);
89 separator = "\n";
90 }
91
92 return buffer.toString();
93 // return String.join("\n", Files.readAllLines(path, UTF_8)); TODO JDK 8
94 }
95
96 /***********************************************************************************************************************************************************
97 * Create a {@link TestResource} for the given name. The actual file will be created under
98 * {@code target/test-artifacts/test-class-simple-name/resourceName}. The expected file should be
99 * placed in {@code src/test/resources/test-class-simple-name/expected-results/resource-name}. Note that the file
100 * actually loaded is the one under {@code target/test-classes} copied there (and eventually filtered) by Maven.
101 * The {@code test-class-simple-name} is tried first with the current test, and then with its eventual
102 * super-classes; this allows to extend existing test suites. Note that if the resource files for a super class are
103 * not in the current project module, they should be explicitly copied here (for instance, by means of the
104 * Maven dependency plugin).
105 *
106 * @param resourceName the name
107 * @return the {@code TestResource}
108 * @throws IOException in case of error
109 **********************************************************************************************************************************************************/
110 @Nonnull
111 public TestResource testResourceFor (@Nonnull final String resourceName)
112 throws IOException
113 {
114 final var testName = test.getClass().getSimpleName();
115 final var expectedFile = findExpectedFilePath(resourceName);
116 final var actualFile = Paths.get("target/test-artifacts", testName, resourceName);
117 Files.createDirectories(actualFile.getParent());
118 return new TestResource(resourceName, actualFile, expectedFile);
119 }
120
121 /***********************************************************************************************************************************************************
122 *
123 **********************************************************************************************************************************************************/
124 @Nonnull
125 private Path findExpectedFilePath (@Nonnull final String resourceName)
126 throws IOException
127 {
128 for (var testClass = test.getClass(); testClass != null; testClass = testClass.getSuperclass())
129 {
130 final var expectedFile =
131 Paths.get("target/test-classes", testClass.getSimpleName(), "expected-results", resourceName);
132
133 if (Files.exists(expectedFile))
134 {
135 return expectedFile;
136 }
137 }
138
139 throw new FileNotFoundException("Expected file for test " + resourceName);
140 }
141
142 /***********************************************************************************************************************************************************
143 * A manipulator of a pair of (actual file, expected file).
144 **********************************************************************************************************************************************************/
145 @RequiredArgsConstructor(access = PRIVATE)
146 public final class TestResource
147 {
148 @Nonnull
149 private final String name;
150
151 @Nonnull
152 private final Path actualFile;
153
154 @Nonnull
155 private final Path expectedFile;
156
157 /***************************************************************************************************************
158 *
159 * Assert that the content of the actual file are the same as the expected file.
160 *
161 * @throws IOException in case of error
162 *
163 **************************************************************************************************************/
164 public void assertActualFileContentSameAsExpected ()
165 throws IOException
166 {
167 assertSameContents(expectedFile.toFile(), actualFile.toFile());
168 }
169
170 /***************************************************************************************************************
171 *
172 * Writes the given strings to the actual file.
173 *
174 * @param strings the strings
175 * @throws IOException in case of error
176 *
177 **************************************************************************************************************/
178 public void writeToActualFile (@Nonnull final String... strings)
179 throws IOException
180 {
181 writeToActualFile(List.of(strings));
182 }
183
184 /***************************************************************************************************************
185 *
186 * Writes the given strings to the actual file.
187 *
188 * @param strings the strings
189 * @throws IOException in case of error
190 *
191 **************************************************************************************************************/
192 public void writeToActualFile (@Nonnull final Iterable<String> strings)
193 throws IOException
194 {
195 Files.write(actualFile, strings, UTF_8);
196 }
197
198 /***************************************************************************************************************
199 *
200 * Writes the given bytes to the actual file.
201 *
202 * @param bytes the bytes
203 * @throws IOException in case of error
204 *
205 **************************************************************************************************************/
206 public void writeToActualFile (@Nonnull final byte[] bytes)
207 throws IOException
208 {
209 Files.write(actualFile, bytes);
210 }
211
212 /***************************************************************************************************************
213 *
214 * Reads the content from the resource file as a single string.
215 *
216 * @return the string
217 * @throws IOException in case of error
218 *
219 **************************************************************************************************************/
220 @Nonnull
221 public String readStringFromResource ()
222 throws IOException
223 {
224 return BaseTestHelper.this.readStringFromResource(name);
225 }
226 }
227 }