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 lombok.RequiredArgsConstructor;
35 import lombok.extern.slf4j.Slf4j;
36 import static java.nio.charset.StandardCharsets.UTF_8;
37 import static it.tidalwave.util.test.FileComparisonUtils.assertSameContents;
38 import static lombok.AccessLevel.PRIVATE;
39
40 /***************************************************************************************************************************************************************
41 *
42 * A facility that provides some common tasks for testing, such as manipulating test files.
43 *
44 * @author Fabrizio Giudici
45 * @since 3.2-ALPHA-18
46 *
47 **************************************************************************************************************************************************************/
48 @RequiredArgsConstructor @Slf4j
49 public class BaseTestHelper
50 {
51 @Nonnull
52 protected final Object test;
53
54 /***********************************************************************************************************************************************************
55 * Returns a {@link Path} for a resource file. The resource should be placed under
56 * {@code src/test/resources/test-class-simple-name/test-resources/resource-name}. Note that the file actually
57 * loaded is the one under {@code target/test-classes} copied there (and eventually filtered) by Maven.
58 *
59 * @param resourceName the resource name
60 * @return the {@code Path}
61 **********************************************************************************************************************************************************/
62 @Nonnull
63 public Path resourceFileFor (@Nonnull final String resourceName)
64 {
65 final var testName = test.getClass().getSimpleName();
66 return Path.of("target/test-classes", testName, "test-resources", resourceName);
67 }
68
69 /***********************************************************************************************************************************************************
70 * Reads the content from the resource file as a single string. See {@link #resourceFileFor(String)} for
71 * further info.
72 *
73 * @param resourceName the resource name
74 * @return the string
75 * @throws IOException in case of error
76 **********************************************************************************************************************************************************/
77 @Nonnull
78 public String readStringFromResource (@Nonnull final String resourceName)
79 throws IOException
80 {
81 final var file = resourceFileFor(resourceName);
82 final var buffer = new StringBuilder();
83 var separator = "";
84
85 for (final var string : Files.readAllLines(file, UTF_8))
86 {
87 buffer.append(separator).append(string);
88 separator = "\n";
89 }
90
91 return buffer.toString();
92 // return String.join("\n", Files.readAllLines(path, UTF_8)); TODO JDK 8
93 }
94
95 /***********************************************************************************************************************************************************
96 * Create a {@link TestResource} for the given name. The actual file will be created under
97 * {@code target/test-artifacts/test-class-simple-name/resourceName}. The expected file should be
98 * placed in {@code src/test/resources/test-class-simple-name/expected-results/resource-name}. Note that the file
99 * actually loaded is the one under {@code target/test-classes} copied there (and eventually filtered) by Maven.
100 * The {@code test-class-simple-name} is tried first with the current test, and then with its eventual
101 * super-classes; this allows to extend existing test suites. Note that if the resource files for a super class are
102 * not in the current project module, they should be explicitly copied here (for instance, by means of the
103 * Maven dependency plugin).
104 *
105 * @param resourceName the name
106 * @return the {@code TestResource}
107 * @throws IOException in case of error
108 **********************************************************************************************************************************************************/
109 @Nonnull
110 public TestResource testResourceFor (@Nonnull final String resourceName)
111 throws IOException
112 {
113 final var testName = test.getClass().getSimpleName();
114 final var expectedFile = findExpectedFilePath(resourceName);
115 final var actualFile = Path.of("target/test-artifacts", testName, resourceName);
116 Files.createDirectories(actualFile.getParent());
117 return new TestResource(resourceName, actualFile, expectedFile);
118 }
119
120 /***********************************************************************************************************************************************************
121 *
122 **********************************************************************************************************************************************************/
123 @Nonnull
124 private Path findExpectedFilePath (@Nonnull final String resourceName)
125 throws IOException
126 {
127 for (var testClass = test.getClass(); testClass != null; testClass = testClass.getSuperclass())
128 {
129 final var expectedFile =
130 Path.of("target/test-classes", testClass.getSimpleName(), "expected-results", resourceName);
131
132 if (Files.exists(expectedFile))
133 {
134 return expectedFile;
135 }
136 }
137
138 throw new FileNotFoundException("Expected file for test " + resourceName);
139 }
140
141 /***********************************************************************************************************************************************************
142 * A manipulator of a pair of (actual file, expected file).
143 **********************************************************************************************************************************************************/
144 @RequiredArgsConstructor(access = PRIVATE)
145 public final class TestResource
146 {
147 @Nonnull
148 private final String name;
149
150 @Nonnull
151 private final Path actualFile;
152
153 @Nonnull
154 private final Path expectedFile;
155
156 /***************************************************************************************************************
157 *
158 * Assert that the content of the actual file are the same as the expected file.
159 *
160 * @throws IOException in case of error
161 *
162 **************************************************************************************************************/
163 public void assertActualFileContentSameAsExpected ()
164 throws IOException
165 {
166 assertSameContents(expectedFile.toFile(), actualFile.toFile());
167 }
168
169 /***************************************************************************************************************
170 *
171 * Writes the given strings to the actual file.
172 *
173 * @param strings the strings
174 * @throws IOException in case of error
175 *
176 **************************************************************************************************************/
177 public void writeToActualFile (@Nonnull final String... strings)
178 throws IOException
179 {
180 writeToActualFile(List.of(strings));
181 }
182
183 /***************************************************************************************************************
184 *
185 * Writes the given strings to the actual file.
186 *
187 * @param strings the strings
188 * @throws IOException in case of error
189 *
190 **************************************************************************************************************/
191 public void writeToActualFile (@Nonnull final Iterable<String> strings)
192 throws IOException
193 {
194 Files.write(actualFile, strings, UTF_8);
195 }
196
197 /***************************************************************************************************************
198 *
199 * Writes the given bytes to the actual file.
200 *
201 * @param bytes the bytes
202 * @throws IOException in case of error
203 *
204 **************************************************************************************************************/
205 public void writeToActualFile (@Nonnull final byte[] bytes)
206 throws IOException
207 {
208 Files.write(actualFile, bytes);
209 }
210
211 /***************************************************************************************************************
212 *
213 * Reads the content from the resource file as a single string.
214 *
215 * @return the string
216 * @throws IOException in case of error
217 *
218 **************************************************************************************************************/
219 @Nonnull
220 public String readStringFromResource ()
221 throws IOException
222 {
223 return BaseTestHelper.this.readStringFromResource(name);
224 }
225 }
226 }