jaulib v1.3.0
Jau Support Library (C++, Java, ..)
FileUtilBaseTest.java
Go to the documentation of this file.
1/*
2 * Author: Sven Gothel <sgothel@jausoft.com>
3 * Copyright (c) 2021 Gothel Software e.K.
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining
6 * a copy of this software and associated documentation files (the
7 * "Software"), to deal in the Software without restriction, including
8 * without limitation the rights to use, copy, modify, merge, publish,
9 * distribute, sublicense, and/or sell copies of the Software, and to
10 * permit persons to whom the Software is furnished to do so, subject to
11 * the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be
14 * included in all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 */
24package jau.test.fs;
25
26import org.jau.fs.CopyOptions;
27import org.jau.fs.FileStats;
28import org.jau.fs.FileUtil;
29import org.jau.fs.TraverseEvent;
30import org.jau.fs.TraverseOptions;
31import org.jau.io.PrintUtil;
32import org.junit.Assert;
33
34import jau.test.junit.util.JunitTracer;
35
36public class FileUtilBaseTest extends JunitTracer {
37 public static final String image_file = "test_data.sqfs";
38 public static final String root = "test_data";
39 // normal location with jaulib as sole project (a)
40 public static final String project_root1a = "../../../test_data";
41 // normal location with jaulib as sole project (b)
42 public static final String project_root1b = "../../../../test_data";
43 // submodule location with jaulib directly hosted below main project (a)
44 public static final String project_root2a = "../../../../jaulib/test_data";
45 // submodule location with jaulib directly hosted below main project (b)
46 public static final String project_root2b = "../../../../../jaulib/test_data";
47 // external filesystem to test ...
48 public static final String project_root_ext = "/mnt/ssd0/data/test_data";
49 // external vfat filesystem destination to test ...
50 public static final String dest_fs_vfat = "/mnt/vfat";
51
52 public static final FileStats getTestDataDirStats() {
53 FileStats path_stats = new FileStats(project_root1a);
54 if( path_stats.exists() ) {
55 return path_stats;
56 }
57 path_stats = new FileStats(project_root1b);
58 if( path_stats.exists() ) {
59 return path_stats;
60 }
61 path_stats = new FileStats(project_root2a);
62 if( path_stats.exists() ) {
63 return path_stats;
64 }
65 path_stats = new FileStats(project_root2b);
66 if( path_stats.exists() ) {
67 return path_stats;
68 }
69 return new FileStats();
70 }
71 public static String getTestDataRelDir() {
72 FileStats path_stats = new FileStats(project_root1a);
73 if( path_stats.exists() ) {
74 return project_root1a;
75 }
76 path_stats = new FileStats(project_root1b);
77 if( path_stats.exists() ) {
78 return project_root1b;
79 }
80 path_stats = new FileStats(project_root2a);
81 if( path_stats.exists() ) {
82 return project_root2a;
83 }
84 path_stats = new FileStats(project_root2b);
85 if( path_stats.exists() ) {
86 return project_root2b;
87 }
88 return "";
89 }
90 public static final FileStats getTestDataImageFile() {
91 FileStats path_stats = new FileStats("../"+image_file);
92 if( path_stats.exists() ) {
93 return path_stats;
94 }
95 path_stats = new FileStats("../../"+image_file);
96 if( path_stats.exists() ) {
97 return path_stats;
98 }
99 return new FileStats();
100 }
101
102 public static class VisitorStats {
104 public int total_real;
107 public int total_no_access;
109 public long total_file_bytes;
110 public int files_real;
111 public int files_sym_link;
112 public int dirs_real;
113 public int dirs_sym_link;
114
115 public VisitorStats(final TraverseOptions topts_) {
116 topts = topts_;
117 total_real = 0;
120 total_no_access = 0;
123 files_real = 0;
124 files_sym_link = 0;
125 dirs_real = 0;
126 dirs_sym_link = 0;
127 }
128
129 public void add(final FileStats element_stats) {
130 if( element_stats.is_link() ) {
131 if( element_stats.exists() ) {
133 } else {
135 }
136 } else {
137 total_real++;
138 }
139 if( !element_stats.has_access() ) {
141 }
142 if( !element_stats.exists() ) {
144 }
145 if( element_stats.is_file() ) {
146 if( element_stats.is_link() ) {
149 total_file_bytes += element_stats.size();
150 }
151 } else {
152 files_real++;
153 total_file_bytes += element_stats.size();
154 }
155 } else if( element_stats.is_dir() ) {
156 if( element_stats.is_link() ) {
158 } else {
159 dirs_real++;
160 }
161 }
162 }
163
164 @Override
165 public boolean equals(final Object other) {
166 if( this == other ) {
167 return true;
168 }
169 if( !( other instanceof VisitorStats ) ) {
170 return false;
171 }
172 final VisitorStats o = (VisitorStats)other;
173 return total_file_bytes == o.total_file_bytes &&
174 total_real == o.total_real &&
175 total_sym_links_existing == o.total_sym_links_existing &&
176 total_sym_links_not_existing == o.total_sym_links_not_existing &&
177 total_no_access == o.total_no_access &&
178 total_not_existing == o.total_not_existing &&
179 files_real == o.files_real &&
180 files_sym_link == o.files_sym_link &&
181 dirs_real == o.dirs_real &&
183 }
184
185 @Override
186 public String toString() {
187 final StringBuilder res = new StringBuilder();
188 res.append( "- traverse_options ").append(topts).append("\n");
189 res.append( "- total_real ").append(total_real).append("\n");
190 res.append( "- total_sym_links_existing ").append(total_sym_links_existing).append("\n");
191 res.append( "- total_sym_links_not_existing ").append(total_sym_links_not_existing).append("\n");
192 res.append( "- total_no_access ").append(total_no_access).append("\n");
193 res.append( "- total_not_existing ").append(total_not_existing).append("\n");
194 res.append( "- total_file_bytes ").append(String.format("%,d", total_file_bytes)).append("\n");
195 res.append( "- files_real ").append(files_real).append("\n");
196 res.append( "- files_sym_link ").append(files_sym_link).append("\n");
197 res.append( "- dirs_real ").append(dirs_real).append("\n");
198 res.append( "- dirs_sym_link ").append(dirs_sym_link).append("\n");
199 return res.toString();
200 }
201 }
202
203 public static class PathStatsVisitor implements FileUtil.PathVisitor {
204 private final VisitorStats stats;
205
206 public PathStatsVisitor(final VisitorStats stats_) {
207 stats = stats_;
208 }
209
210 @Override
211 public boolean visit(final TraverseEvent tevt, final FileStats item_stats, final long depth) {
212 // PrintUtil.fprintf_td(System.err, "add: item_stats "+item_stats+", tevt "+tevt+"\n");
213 stats.add(item_stats);
214 return true;
215 }
216 }
217
218 static class source_visitor_params {
219 public String title;
220 public String source_folder_path;
221 public FileStats dest;
222 public boolean dest_is_vfat;
223 public boolean opt_drop_dest_links;
224
225 public source_visitor_params(final String t, final String sfp, final FileStats d, final boolean dest_is_vfat_, final boolean opt_drop_dest_links_) {
226 title = t;
227 source_folder_path = sfp;
228 dest = d;
229 dest_is_vfat = dest_is_vfat_;
230 opt_drop_dest_links = opt_drop_dest_links_;
231 }
232 };
233
234 static class dest_visitor_params {
235 public String title;
236 public String source_folder_path;
237 public String dest_folder_path;
238 public String source_basename;
239 public FileStats stats;
240 public boolean dest_is_vfat;
241 public boolean match;
242 public dest_visitor_params(final String t, final String sfp, final String dfp, final String sb, final FileStats s, final boolean dest_is_vfat_) {
243 title = t;
244 source_folder_path = sfp;
245 dest_folder_path = dfp;
246 source_basename = sb;
247 stats = s;
248 dest_is_vfat = dest_is_vfat_;
249 match = false;
250 }
251 };
252
253 public void testxx_copy_r_p(final String title, final FileStats source, final int source_added_dead_links,
254 final String dest,
255 final CopyOptions copts,
256 final boolean dest_is_vfat) {
257 Assert.assertTrue( source.exists() );
258 Assert.assertTrue( source.is_dir() );
259
260 final boolean dest_is_parent;
261 final String dest_root;
262 {
263 final FileStats dest_stats = new FileStats(dest);
264 if( dest_stats.exists() ) {
265 // If dest_path exists as a directory, source_path dir will be copied below the dest_path directory
266 // _if_ copy_options::into_existing_dir is not set. Otherwise its content is copied into the existing dest_path.
267 Assert.assertTrue( dest_stats.is_dir() );
269 dest_is_parent = false;
270 dest_root = dest;
271 } else {
272 dest_is_parent = true;
273 dest_root = dest + "/" + source.item().basename();
274 }
275 } else {
276 // If dest_path doesn't exist, source_path dir content is copied into the newly created dest_path.
277 dest_is_parent = false;
278 dest_root = dest;
279 }
280 }
281 PrintUtil.fprintf_td(System.err, "%s: source %s, dest[arg %s, is_parent %b, dest_root %s], copts %s, dest_is_vfat %b\n",
282 title, source, dest, dest_is_parent, dest_root, copts, dest_is_vfat);
283
284 final boolean opt_follow_links = copts.isSet(CopyOptions.Bit.follow_symlinks);
285 final boolean opt_drop_dest_links = !opt_follow_links && copts.isSet(CopyOptions.Bit.ignore_symlink_errors);
286
287 Assert.assertTrue( true == FileUtil.copy(source.path(), dest, copts) );
288
289 final FileStats dest_stats = new FileStats(dest_root);
290 Assert.assertTrue( true == dest_stats.exists() );
291 Assert.assertTrue( true == dest_stats.ok() );
292 Assert.assertTrue( true == dest_stats.is_dir() );
293
294 {
295 final TraverseOptions topts = new TraverseOptions();
298
299 final VisitorStats stats = new VisitorStats(topts);
300 final VisitorStats stats_copy = new VisitorStats(topts);
301
302 final PathStatsVisitor pv_orig = new PathStatsVisitor(stats);
303 final PathStatsVisitor pv_copy = new PathStatsVisitor(stats_copy);
304
305 Assert.assertTrue( true == FileUtil.visit(source, topts, pv_orig) );
306 Assert.assertTrue( true == FileUtil.visit(dest_stats, topts, pv_copy) );
307
308 PrintUtil.fprintf_td(System.err, "%s: copy %s, traverse %s\n", title, copts, topts);
309 PrintUtil.fprintf_td(System.err, "%s: source visitor stats\n%s\n", title, stats);
310 PrintUtil.fprintf_td(System.err, "%s: destination visitor stats\n%s\n", title, stats_copy);
311
312 Assert.assertTrue( 7 == stats.total_real );
313 Assert.assertTrue( 10 - source_added_dead_links == stats.total_sym_links_existing );
314 Assert.assertTrue( 4 + source_added_dead_links == stats.total_sym_links_not_existing );
315 Assert.assertTrue( 0 == stats.total_no_access );
316 Assert.assertTrue( 4 + source_added_dead_links == stats.total_not_existing );
317 Assert.assertTrue( 60 == stats.total_file_bytes );
318 Assert.assertTrue( 4 == stats.files_real );
319 Assert.assertTrue( 9 - source_added_dead_links == stats.files_sym_link );
320 Assert.assertTrue( 3 == stats.dirs_real );
321 Assert.assertTrue( 1 == stats.dirs_sym_link );
322
323 if( ( !opt_follow_links && !opt_drop_dest_links ) ||
324 ( opt_drop_dest_links && 0 < stats_copy.total_sym_links_existing )
325 )
326 {
327 // 1:1 exact copy
328 Assert.assertTrue( 7 == stats_copy.total_real );
329 Assert.assertTrue( 9 == stats_copy.total_sym_links_existing );
330 Assert.assertTrue( 5 == stats_copy.total_sym_links_not_existing ); // symlink ../README.txt + 4 dead_link*
331 Assert.assertTrue( 0 == stats_copy.total_no_access );
332 Assert.assertTrue( 5 == stats_copy.total_not_existing ); // symlink ../README.txt + 4 dead_link*
333 Assert.assertTrue( 60 == stats_copy.total_file_bytes );
334 Assert.assertTrue( 4 == stats_copy.files_real );
335 Assert.assertTrue( 8 == stats_copy.files_sym_link );
336 Assert.assertTrue( 3 == stats_copy.dirs_real );
337 Assert.assertTrue( 1 == stats_copy.dirs_sym_link );
338 } else if( opt_drop_dest_links ) {
339 // destination filesystem has no symlink support, i.e. vfat
340 Assert.assertTrue( 7 == stats_copy.total_real );
341 Assert.assertTrue( 0 == stats_copy.total_sym_links_existing );
342 Assert.assertTrue( 0 == stats_copy.total_sym_links_not_existing ); // symlink ../README.txt + 4 dead_link*
343 Assert.assertTrue( 0 == stats_copy.total_no_access );
344 Assert.assertTrue( 0 == stats_copy.total_not_existing ); // symlink ../README.txt + 4 dead_link*
345 Assert.assertTrue( 60 == stats_copy.total_file_bytes );
346 Assert.assertTrue( 4 == stats_copy.files_real );
347 Assert.assertTrue( 0 == stats_copy.files_sym_link );
348 Assert.assertTrue( 3 == stats_copy.dirs_real );
349 Assert.assertTrue( 0 == stats_copy.dirs_sym_link );
350 } else if( opt_follow_links ) {
351 // followed symlinks
352 Assert.assertTrue( 20 == stats_copy.total_real );
353 Assert.assertTrue( 0 == stats_copy.total_sym_links_existing );
354 Assert.assertTrue( 0 == stats_copy.total_sym_links_not_existing ); // symlink ../README.txt + 4 dead_link*
355 Assert.assertTrue( 0 == stats_copy.total_no_access );
356 Assert.assertTrue( 0 == stats_copy.total_not_existing ); // symlink ../README.txt + 4 dead_link*
357 Assert.assertTrue( 60 < stats_copy.total_file_bytes );
358 Assert.assertTrue( 16 == stats_copy.files_real );
359 Assert.assertTrue( 0 == stats_copy.files_sym_link );
360 Assert.assertTrue( 4 == stats_copy.dirs_real );
361 Assert.assertTrue( 0 == stats_copy.dirs_sym_link );
362 }
363 }
364 {
365 // compare each file in detail O(n*n)
366 final TraverseOptions topts = new TraverseOptions();
369
370 final source_visitor_params svp = new source_visitor_params(title, source.path(), dest_stats, dest_is_vfat, opt_drop_dest_links);
371 final FileUtil.PathVisitor pv1 = new FileUtil.PathVisitor() {
372 @Override
373 public boolean visit(final TraverseEvent tevt1, final FileStats element_stats1, final long depth) {
374 final dest_visitor_params dvp = new dest_visitor_params(svp.title, svp.source_folder_path, svp.dest.path(), FileUtil.basename(element_stats1.path() ), element_stats1, svp.dest_is_vfat);
375 final FileUtil.PathVisitor pv2 = new FileUtil.PathVisitor() {
376 @Override
377 public boolean visit(final TraverseEvent tevt2, final FileStats element_stats2, final long depth2) {
378 final String path2 = element_stats2.path();
379 final String basename2 = FileUtil.basename( path2 );
380 final String source_folder_basename = FileUtil.basename( dvp.source_folder_path );
381 if( basename2.equals( dvp.source_basename ) ||
382 ( source_folder_basename.equals( dvp.source_basename ) && dvp.dest_folder_path.equals( path2 ) )
383 )
384 {
385 boolean attr_equal, bit_equal;
386 if( "README_slink08_relext.txt".equals(basename2) || 0 == basename2.indexOf("dead_link") ) {
387 // symlink to ../README.txt not existent on target
388 // dead_link* files intentionally not existant
389 attr_equal = element_stats2.is_link() &&
390 !element_stats2.exists();
391
392 bit_equal = true; // pretend
393 } else {
394 if( !dvp.dest_is_vfat ) {
395 // full attribute check
396 attr_equal =
397 element_stats2.mode().equals( dvp.stats.mode() ) &&
398 // element_stats2.atime().equals( dvp.stats.atime() ) && // destination access-time may differ due to processing post copy
399 element_stats2.mtime().equals( dvp.stats.mtime() ) &&
400 element_stats2.uid() == dvp.stats.uid() &&
401 element_stats2.gid() == dvp.stats.gid() &&
402 element_stats2.size() == dvp.stats.size();
403 } else {
404 // minimal vfat attribute check
405 // const jau::fraction_timespec td(5_s);
406 final long td_ms = 5000;
407
408 attr_equal =
409 // ( element_stats2.mode() & jau::fs::fmode_t::rwx_usr ) == ( dvp.stats.mode() & jau::fs::fmode_t::rwx_usr ) &&
410 // element_stats2.atime().equals( dvp.stats.atime() ) && // destination access-time may differ due to processing post copy
411 Math.abs( element_stats2.mtime().toEpochMilli() - dvp.stats.mtime().toEpochMilli() ) <= td_ms &&
412 element_stats2.uid() == dvp.stats.uid() &&
413 // element_stats2.gid() == dvp.stats.gid() &&
414 element_stats2.size() == dvp.stats.size();
415 }
416 if( dvp.stats.is_file() ) {
417 bit_equal = FileUtil.compare(dvp.stats.path(), element_stats2.path(), true);
418 } else {
419 bit_equal = true; // pretend
420 }
421 }
422 dvp.match = attr_equal && bit_equal;
423 PrintUtil.fprintf_td(System.err, "%s.check: '%s', match [attr %b, bit %b -> %b]\n\t source %s\n\t dest__ %s\n\n",
424 dvp.title, basename2, attr_equal, bit_equal, dvp.match,
425 dvp.stats,
426 element_stats2);
427 return false; // done
428 } else {
429 return true; // continue search
430 }
431 } };
432 if( FileUtil.visit(svp.dest, topts, pv2) ) {
433 // not found
434 final boolean ignore = element_stats1.is_link() && svp.opt_drop_dest_links;
435 PrintUtil.fprintf_td(System.err, "%s.check: %s: '%s', not found!\n\t source %s\n\n",
436 svp.title, ignore ? "Ignored" : "Error", dvp.source_basename, element_stats1);
437 return ignore;
438 } else {
439 // found
440 if( dvp.match ) {
441 return true; // found and matching, continue
442 } else {
443 return false; // found not matching, abort
444 }
445 }
446 } };
447 Assert.assertTrue( true == FileUtil.visit(source, topts, pv1) );
448 }
449 }
450
451}
boolean visit(final TraverseEvent tevt, final FileStats item_stats, final long depth)
void add(final FileStats element_stats)
VisitorStats(final TraverseOptions topts_)
static final FileStats getTestDataImageFile()
static final String project_root_ext
static final String project_root1a
static final String project_root2b
static final String project_root1b
static final String project_root2a
static final FileStats getTestDataDirStats()
void testxx_copy_r_p(final String title, final FileStats source, final int source_added_dead_links, final String dest, final CopyOptions copts, final boolean dest_is_vfat)
static final String image_file
static final String dest_fs_vfat
Filesystem copy options used to copy() path elements.
boolean isSet(final Bit bit)
String basename()
Return the basename, shall not be empty nor contain a dirname.
Definition: DirItem.java:63
boolean equals(final Object other)
Definition: FMode.java:149
Platform agnostic representation of POSIX ::lstat() and ::stat() for a given pathname.
Definition: FileStats.java:41
String path()
Returns the unix path representation.
Definition: FileStats.java:261
boolean is_file()
Returns true if entity is a file, might be in combination with is_link().
Definition: FileStats.java:386
Instant mtime()
Returns the last modification time of this element since Unix Epoch.
Definition: FileStats.java:350
boolean ok()
Returns true if no error occurred.
Definition: FileStats.java:356
boolean exists()
Returns true if entity does not exist, exclusive bit.
Definition: FileStats.java:395
boolean has_access()
Returns true if entity gives no access to user, exclusive bit.
Definition: FileStats.java:392
int gid()
Returns the group id, owning the element.
Definition: FileStats.java:334
boolean is_link()
Returns true if entity is a symbolic link, might be in combination with is_file(),...
Definition: FileStats.java:389
DirItem item()
Returns the dir_item.
Definition: FileStats.java:250
FMode mode()
Returns the FMode, file type and mode.
Definition: FileStats.java:315
boolean is_dir()
Returns true if entity is a directory, might be in combination with is_link().
Definition: FileStats.java:383
int uid()
Returns the user id, owning the element.
Definition: FileStats.java:331
long size()
Returns the size in bytes of this element if is_file(), otherwise zero.
Definition: FileStats.java:341
Native file types and functionality.
Definition: FileUtil.java:33
static boolean copy(final String source_path, final String dest_path, final CopyOptions copts)
Copy the given source_path to dest_path using copy_options.
Definition: FileUtil.java:356
static native String basename(final String path)
Return stripped leading directory components from given path separated by /.
static native boolean compare(final String source1, final String source2, final boolean verbose)
Compare the bytes of both files, denoted by source1 and source2.
static boolean visit(final String path, final TraverseOptions topts, final PathVisitor visitor)
Visit element(s) of a given path, see traverse_options for detailed settings.
Definition: FileUtil.java:201
Filesystem traverse options used to visit() path elements.
TraverseOptions set(final Bit bit)
Sets the given bit and returns this instance for chaining.
boolean isSet(final Bit bit)
static void fprintf_td(final PrintStream out, final String format, final Object ... args)
Convenient PrintStream#printf(String, Object...) invocation, prepending the elapsedTimeMillis() times...
Definition: PrintUtil.java:37
follow_symlinks
Copy referenced symbolic linked files or directories instead of just the symbolic link with property ...
ignore_symlink_errors
Ignore errors from erroneous symlinks, e.g.
into_existing_dir
Copy source dir content into an already existing destination directory as if destination directory di...
Filesystem traverse event used to call path_visitor for path elements from visit().
follow_symlinks
Traverse through symbolic linked directories if traverse_options::recursive is set,...
dir_entry
Visit the content's parent directory at entry.
recursive
Traverse through directories, i.e.
Path visitor for FileUtil#visit(FileStats, TraverseOptions, PathVisitor).
Definition: FileUtil.java:172