View Javadoc
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   }