View Javadoc
1   /*
2    * Licensed under the Apache License, Version 2.0 (the "License");
3    * you may not use this file except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    * http://www.apache.org/licenses/LICENSE-2.0
7    *
8    * Unless required by applicable law or agreed to in writing, software
9    * distributed under the License is distributed on an "AS IS" BASIS,
10   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11   * See the License for the specific language governing permissions and
12   * limitations under the License.
13   */
14  
15  package de.softwareforge.testing.postgres.embedded;
16  
17  import static com.google.common.base.Preconditions.checkArgument;
18  import static com.google.common.base.Preconditions.checkState;
19  
20  import jakarta.annotation.Nonnull;
21  import java.io.File;
22  import java.io.IOException;
23  import java.net.ServerSocket;
24  import java.nio.file.Files;
25  import java.nio.file.Path;
26  import java.time.Duration;
27  import java.util.Comparator;
28  import java.util.Locale;
29  import java.util.Objects;
30  import java.util.concurrent.ThreadLocalRandom;
31  import java.util.stream.Stream;
32  
33  import com.google.common.base.Joiner;
34  import com.google.common.base.Strings;
35  import com.google.common.collect.ImmutableList;
36  
37  final class EmbeddedUtil {
38  
39      static final String OS_NAME;
40      static final String OS_ARCH;
41  
42      static final boolean IS_OS_WINDOWS;
43      static final boolean IS_OS_MAC;
44      static final boolean IS_OS_LINUX;
45      static final boolean IS_ALPINE_LINUX;
46  
47      static final boolean IS_ARCH_X86_64;
48      static final boolean IS_ARCH_AARCH64;
49      static final boolean IS_ARCH_AARCH32;
50  
51      private static final String ALPHANUM;
52      private static final String LOWERCASE;
53  
54      static {
55          OS_NAME = getSystemProperty("os.name");
56          OS_ARCH = getSystemProperty("os.arch");
57  
58          IS_OS_WINDOWS = getOsMatchesName("Windows");
59          IS_OS_LINUX = getOsMatchesName("Linux");
60          IS_OS_MAC = getOsMatchesName("Mac");
61  
62          IS_ARCH_X86_64 = OS_ARCH.equals("x86_64") || OS_ARCH.equals("amd64");
63          IS_ARCH_AARCH64 = OS_ARCH.equals("aarch64");
64          IS_ARCH_AARCH32 = OS_ARCH.equals("aarch32") || OS_ARCH.equals("arm");
65  
66          // this is a glorious hack
67          IS_ALPINE_LINUX = new File("/etc/alpine-release").exists();
68  
69          String numbers = sequence('0', 10);
70          LOWERCASE = sequence('a', 26);
71          String uppercase = sequence('A', 26);
72          ALPHANUM = numbers + LOWERCASE + uppercase;
73      }
74  
75      private EmbeddedUtil() {
76          throw new AssertionError("EmbeddedUtil can not be instantiated");
77      }
78  
79      static File getWorkingDirectory() {
80          File parent = new File(System.getProperty("java.io.tmpdir"));
81          // personalize the unpack folder to allow systems with many users using the same tmp folder to work
82          File workDir = new File(parent, "embedded-pg-" + Objects.requireNonNullElse(System.getProperty("user.name"), "unknown"));
83  
84          ensureDirectory(workDir);
85  
86          return workDir;
87      }
88  
89      static void ensureDirectory(@Nonnull File workDir) {
90  
91          long retryCount = 5;
92  
93          while (retryCount > 0) {
94              if (workDir.exists()) {
95                  break;
96              }
97              if (workDir.mkdirs()) {
98                  break;
99              }
100             retryCount--;
101 
102             try {
103                 Thread.sleep(100 * (6 - retryCount));
104             } catch (InterruptedException e) {
105                 Thread.currentThread().interrupt();
106                 throw new IllegalStateException("Could not create working directory'" + workDir.getAbsolutePath() + "'", e);
107             }
108 
109         }
110         if (retryCount == 0) {
111             throw new IllegalStateException("Could not create working directory '" + workDir.getAbsolutePath() + "' after 5 tries");
112         }
113 
114         checkState(workDir.exists(), "'%s' does not exist!", workDir);
115         checkState(workDir.isDirectory(), "'%s' exists but is not a directory!", workDir);
116 
117         if (!workDir.canWrite()) {
118             checkState(workDir.setWritable(true, false), "Could not make directory '%s' writeable!", workDir);
119         }
120 
121         checkState(workDir.canWrite(), "'%s' is a directory but can not be written!", workDir);
122     }
123 
124     //
125     // taken from apache commons io
126     //
127     static String getFileBaseName(final String fileName) {
128         if (fileName == null) {
129             return null;
130         }
131         failIfNullBytePresent(fileName);
132         final int index = indexOfLastSeparator(fileName);
133         return fileName.substring(index + 1);
134     }
135 
136     private static void failIfNullBytePresent(final String path) {
137         final int len = path.length();
138         for (int i = 0; i < len; i++) {
139             checkArgument(path.charAt(i) != 0,
140                     "Null byte present in file/path name.");
141         }
142     }
143 
144     private static int indexOfLastSeparator(final String fileName) {
145         if (fileName == null) {
146             return -1;
147         }
148         final int lastUnixPos = fileName.lastIndexOf('/'); // unix
149         final int lastWindowsPos = fileName.lastIndexOf('\\'); // windows
150         return Math.max(lastUnixPos, lastWindowsPos);
151     }
152 
153     //
154     // taken from apache commons io
155     //
156 
157     static void rmdirs(File dir) throws IOException {
158         if (dir.exists() && dir.isDirectory()) {
159             try (Stream<Path> walk = Files.walk(dir.toPath())) {
160                 walk.sorted(Comparator.reverseOrder())
161                         .map(Path::toFile)
162                         .forEach(File::delete);
163             }
164         }
165     }
166 
167     static String formatDuration(Duration duration) {
168         long millis = duration.toMillis();
169         long hours;
170         long minutes;
171         long secs;
172         long ms;
173         ImmutableList.Builder<String> builder = ImmutableList.builder();
174         if (millis == 0) {
175             builder.add("0 ms");
176         } else {
177             long seconds = millis / 1000L;
178             hours = seconds / 3600L;
179             minutes = (seconds % 3600L) / 60L;
180             secs = (seconds % 60L);
181             ms = millis % 1000L;
182 
183             if (hours != 0) {
184                 builder.add(hours + " hours");
185             }
186             if (minutes != 0) {
187                 builder.add(minutes + " minutes");
188             }
189             if (secs != 0) {
190                 builder.add(secs + " seconds");
191             }
192             if (ms != 0) {
193                 builder.add(ms + " ms");
194             }
195         }
196 
197         return Joiner.on(' ').join(builder.build());
198     }
199 
200     static int allocatePort() throws IOException {
201         try (ServerSocket socket = new ServerSocket(0)) {
202             while (!socket.isBound()) {
203                 Thread.sleep(50);
204             }
205             return socket.getLocalPort();
206         } catch (InterruptedException e) {
207             Thread.currentThread().interrupt();
208             throw new IOException("Thread interrupted!", e);
209         }
210     }
211 
212     static String randomAlphaNumeric(int length) {
213         return randomString(ALPHANUM, length);
214     }
215 
216     static String randomLowercase(int length) {
217         return randomString(LOWERCASE, length);
218     }
219 
220     private static String sequence(char start, int count) {
221         StringBuilder sb = new StringBuilder();
222         for (int i = 0; i < count; i++) {
223             sb.append((char) (start + i));
224         }
225         return sb.toString();
226     }
227 
228     private static String randomString(String alphabet, int length) {
229         StringBuilder sb = new StringBuilder();
230         for (int i = 0; i < length; i++) {
231             int random = ThreadLocalRandom.current().nextInt(alphabet.length());
232             sb.append(alphabet.charAt(random));
233         }
234         return sb.toString();
235     }
236 
237     private static String getSystemProperty(String propertyName) {
238         try {
239             return Strings.nullToEmpty(System.getProperty(propertyName, ""));
240         } catch (SecurityException e) {
241             return "<unknown>";
242         }
243     }
244 
245     private static boolean getOsMatchesName(final String osNamePrefix) {
246         return OS_NAME.toLowerCase(Locale.ROOT).startsWith(osNamePrefix.toLowerCase(Locale.ROOT));
247     }
248 }