openfoam/applications/test/fileName/Test-fileName.C
Mark Olesen d5cdc60a54 BUG: processorMeshes removeFiles does not remove collated (fixes #2607)
ENH: extend rmDir to handle removal of empty directories only

- recursively remove directories that only contain other directories
  but no other contents. Treats dead links as non-content.
2022-10-11 17:58:22 +02:00

865 lines
24 KiB
C

/*---------------------------------------------------------------------------*\
========= |
\\ / F ield | OpenFOAM: The Open Source CFD Toolbox
\\ / O peration |
\\ / A nd | www.openfoam.com
\\/ M anipulation |
-------------------------------------------------------------------------------
Copyright (C) 2011-2016 OpenFOAM Foundation
Copyright (C) 2016-2022 OpenCFD Ltd.
-------------------------------------------------------------------------------
License
This file is part of OpenFOAM.
OpenFOAM is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
for more details.
You should have received a copy of the GNU General Public License
along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
Application
Test-fileName
Description
Test some basic fileName functionality
\*---------------------------------------------------------------------------*/
#include "argList.H"
#include "fileName.H"
#include "SubList.H"
#include "DynamicList.H"
#include "IOobject.H"
#include "IOstreams.H"
#include "OSspecific.H"
#include "POSIX.H"
#include "Switch.H"
#include "etcFiles.H"
#include "Pair.H"
#include "Tuple2.H"
#include <fstream>
using namespace Foam;
// Create named file with some dummy content
void touchFileContent(const fileName& file)
{
std::ofstream os(file);
os << "file=<" << file << ">" << nl;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
unsigned testClean(std::initializer_list<Pair<std::string>> tests)
{
unsigned nFail = 0;
for (const Pair<std::string>& test : tests)
{
const std::string& input = test.first();
const std::string& expected = test.second();
fileName cleaned(test.first());
cleaned.clean(); // Remove unneeded ".."
if (cleaned == expected)
{
Info<< "(pass)"
<< " clean " << input << " -> " << cleaned << nl;
}
else
{
Info<< "(fail)"
<< " clean " << input << " -> " << cleaned
<< " expected=" << expected
<< nl;
++nFail;
}
}
return nFail;
}
unsigned testStrip
(
const bool doClean,
std::initializer_list
<
Tuple2<bool, std::string>
> tests
)
{
Info<< nl << "Checking with clean=" << Switch(doClean) << nl << endl;
unsigned nFail = 0;
for (const Tuple2<bool, std::string>& test : tests)
{
const bool expected = test.first();
const std::string& input = test.second();
fileName output(fileName::validate(input, doClean));
// Check for real failure (invalid chars) vs.
// spurious failure (removed double slashes with 'doClean').
const bool same =
(
doClean
? fileName::equals(input, output)
: (input == output)
);
if (same)
{
if (expected)
{
Info<< "(pass) validated " << input << " = " << output << nl;
}
else
{
++nFail;
Info<< "(fail) unexpected success for " << input << nl;
}
}
else
{
if (expected)
{
++nFail;
Info<< "(fail) unexpected";
}
else
{
Info<< "(pass) expected";
}
Info<< " failure for " << input << nl;
}
}
return nFail;
}
unsigned testEquals
(
std::initializer_list
<
Tuple2<bool, Pair<std::string>>
> tests
)
{
Info<< nl << "Checking fileName::equals()" << nl << endl;
unsigned nFail = 0;
for (const Tuple2<bool, Pair<std::string>>& test : tests)
{
const bool expected = test.first();
const std::string& s1 = test.second().first();
const std::string& s2 = test.second().second();
const bool same = fileName::equals(s1, s2);
if (same)
{
if (expected)
{
Info<< "(pass) success";
}
else
{
++nFail;
Info<< "(fail) unexpected success";
}
}
else
{
if (expected)
{
++nFail;
Info<< "(fail) unexpected failure";
}
else
{
Info<< "(pass) expected failure";
}
}
Info<< " for " << s1 << " == " << s2 << nl;
}
return nFail;
}
unsigned testRelative(std::initializer_list<Pair<std::string>> tests)
{
Info<< nl << "Checking fileName::relative()" << nl << endl;
unsigned nFail = 0;
for (const Pair<std::string>& test : tests)
{
const std::string& dir = test.first();
const std::string& parent = test.second();
Info<< "directory: " << dir << nl
<< "parent : " << parent << nl
<< "relative = " << fileName(dir).relative(parent) << nl
<< "case-rel = " << fileName(dir).relative(parent, true) << nl
<< endl;
}
return nFail;
}
void testDirname(const fileName& input)
{
Info<< "input:" << input
<< " path:" << input.path()
<< " name:\"" << input.name() << '"'
<< " ext:\"" << input.ext() << '"'
<< " components: " << flatOutput(input.components())
<< " last: " << input.component(string::npos) << nl;
}
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
// Main program:
int main(int argc, char *argv[])
{
argList::noParallel();
argList::addBoolOption("validate", "test fileName::validate");
argList::addBoolOption("ext", "test handing of file extensions");
argList::addBoolOption("construct", "test constructors");
argList::addBoolOption("relative", "test relative operations");
argList::addBoolOption("system", "test filesystem operations");
argList::addBoolOption("default", "reinstate default tests");
argList::addBoolOption("clean", "clean()");
argList::addBoolOption("dirname", "basename/dirname tables");
argList::addNote("runs default tests or specified ones only");
#include "setRootCase.H"
// Run default tests, unless specific tests are requested
const bool defaultTests =
args.found("default") || args.options().empty();
if (args.found("construct"))
{
Info<< "From initializer_list<word> = ";
fileName file1
{
"hello",
"hello1",
"hello2",
"hello3",
"hello4.ext"
};
Info<< file1 << nl;
Info<< "From initializer_list<fileName> = ";
fileName file2
{
file1,
"some",
"more/things.ext"
};
Info<< file2 << nl;
Info<< "From initializer_list<fileName> with nesting = ";
fileName file3
{
std::string("ffO"),
"some",
"more/things.ext"
};
Info<< file3 << nl;
DynamicList<word> base
{
"hello",
"hello1"
};
fileName file4
{
"some",
file3,
"more/things.ext",
file1
};
Info<< "All ==> " << file4 << nl;
}
if (args.found("dirname"))
{
testDirname("");
testDirname(".");
testDirname("abc");
testDirname("/");
testDirname("/abc");
testDirname("abc/def");
testDirname("/abc/def");
testDirname("/abc/def/");
testDirname("/abc///def///");
testDirname("/abc/../def");
}
// Test various ext() methods
if (args.found("ext"))
{
Info<<nl << nl << "handling of fileName extension" << nl;
fileName empty;
fileName endWithDot("some.path/name.");
fileName endWithSlash("some.path/");
fileName input0("some.file/with.out/extension");
fileName input1("path.to/media/image.png");
Info<<"File : " << input0 << " ext: "
<< Switch(input0.has_ext())
<< " = " << input0.ext() << nl;
Info<<"File : " << input1 << " ext: "
<< Switch(input1.has_ext())
<< " = " << input1.ext() << nl;
Info<<"File : " << endWithDot << " ext: "
<< Switch(endWithDot.has_ext())
<< " = " << endWithDot.ext() << " <-- perhaps return false?" << nl;
Info<<"File : " << endWithSlash << " ext: "
<< Switch(endWithSlash.has_ext())
<< " = " << endWithSlash.ext() << nl;
Info<<"Remove extension " << (input0.remove_ext());
Info<< " now: " << input0 << nl;
Info<<"Remove extension " << (input1.removeExt());
Info<< " now: " << input1 << nl;
Info<<"Remove extension " << (endWithSlash.remove_ext());
Info<< " now: " << endWithSlash << nl;
wordList exts{ "jpg", "png", "txt", word::null };
Info<<"Add extension(s): " << input1 << nl;
for (const word& e : exts)
{
Info<<"<" << e << "> -> " << input1.ext(e) << nl;
}
Info<< nl;
Info<<"Test has_ext(word)" << nl
<<"~~~~~~~~~~~~~~~~~" << nl;
Info<<"Has extension(s):" << nl
<< "input: " << input1 << nl;
for (const word& e : exts)
{
Info<<" '" << e << "' -> "
<< Switch(input1.has_ext(e)) << nl;
}
Info<< nl;
Info<<"Has extension(s):" << nl
<< "input: " << endWithDot << nl;
for (const word& e : exts)
{
Info<<" '" << e << "' -> "
<< Switch(endWithDot.has_ext(e)) << nl;
}
Info<< nl;
Info<<"Test has_ext(wordRe)" << nl
<<"~~~~~~~~~~~~~~~~~~~" << nl;
// A regex with a zero length matcher doesn't work at all:
// eg "(png|jpg|txt|)" regex matcher itself
wordRe matcher0("()", wordRe::REGEX);
wordRe matcher1("(png|jpg|txt)", wordRe::REGEX);
wordRe matcher2("(png|txt)", wordRe::REGEX);
Info<<"Has extension(s):" << nl
<< "input: " << endWithDot << nl;
Info<<" " << matcher0 << " -> "
<< Switch(endWithDot.has_ext(matcher0)) << nl;
Info<<" " << matcher1 << " -> "
<< Switch(endWithDot.has_ext(matcher1)) << nl;
Info<<" " << matcher2 << " -> "
<< Switch(endWithDot.has_ext(matcher2)) << nl;
Info<< "input: " << input1 << nl;
Info<<" " << matcher0 << " -> "
<< Switch(input1.has_ext(matcher0)) << nl;
Info<<" " << matcher1 << " -> "
<< Switch(input1.has_ext(matcher1)) << nl;
Info<<" " << matcher2 << " -> "
<< Switch(input1.has_ext(matcher2)) << nl;
Info<< nl;
Info<<"Remove extension(s):" << nl << "input: " << input1 << nl;
while (!input1.empty())
{
if (input1.remove_ext())
{
Info<< " -> " << input1 << nl;
}
else
{
Info<< "stop> " << input1 << nl;
break;
}
}
Info<< nl;
input0.clear();
Info<<"test with zero-sized: " << input0 << nl;
Info<<"add extension: " << input0.ext("abc") << nl;
Info<< nl;
input0 = "this/";
Info<<"test add after slash: " << input0 << nl;
Info<<"add extension: " << input0.ext("abc")
<< " <-- avoids accidentally creating hidden files" << nl;
Info<< nl;
input0 = "this.file.";
Info<<"test after dot: " << input0 << nl;
Info<<"add extension: " << input0.ext("abc")
<< " <-- No check for repeated dots (user error!)" << nl;
Info<< nl;
}
if (args.found("clean"))
{
Info<< nl << "Test fileName::clean()" << nl << nl;
unsigned nFail = testClean
({
{ "/", "/" },
{ "/abc/", "/abc" },
{ "/abc////def", "/abc/def" },
{ "/abc/def/./ghi/.", "/abc/def/ghi" },
{ "abc/def/./", "abc/def" },
{ "./abc/", "./abc" },
{ "/abc/def/../ghi/jkl/nmo/..", "/abc/ghi/jkl" },
{ "abc/../def/ghi/../jkl", "abc/../def/jkl" },
});
Info<< nl;
if (nFail)
{
Info<< "failed " << nFail;
}
else
{
Info<< "passed all";
}
Info<< " fileName::clean tests" << nl;
}
if (args.found("validate"))
{
unsigned nFail = 0;
Info<< nl << "Test fileName::validate" << nl;
// Without clean
nFail += testEquals
(
{
{ true, { "abc", "abc/" } },
{ true, { "///abc/", "//abc///" } },
{ false, { " ab //c/", "ab/c" } },
}
);
Info<< nl << "Test fileName::validate" << nl;
// Without clean
nFail += testStrip
(
false,
{
{ true, "abc/" },
{ true, "/", },
{ true, "//", },
{ true, "/abc/def", },
{ true, "/abc/def/", },
{ false, "/abc def" },
{ true, "/abc////def///", },
{ false, "/abc//// def///" },
}
);
// With clean
nFail += testStrip
(
true,
{
{ true, "abc/" },
{ true, "/" },
{ true, "//" },
{ true, "/abc/def" },
{ true, "/abc/def/" },
{ false, "/abc def" },
{ true, "/abc////def///" },
{ false, "/abc//// def///" },
}
);
Info<< nl;
if (nFail)
{
Info<< "failed " << nFail;
}
else
{
Info<< "passed all";
}
Info<< " fileName::validate tests" << nl;
}
if (args.found("relative"))
{
unsigned nFail = 0;
nFail += testRelative
(
{
{ "", "" },
{ "", "/" },
{ "", "/some" },
{ "/some/dir/subdir/name", "" },
{ "/some/dir/subdir/name", "/" },
{ "/some/dir/subdir/name", "/some" },
{ "/some/dir/subdir/name", "/some/" },
{ "/some/dir/subdir/name", "/some/dir" },
{ "/some/dir/subdir/name", "/some/dir/" },
{ "/some/dir/subdir/name", "/some/dir/subdir" },
{ "/some/dir/subdir/name", "/some/dir/subdir/name" },
{ "/some/dir/subdir/name", "/some/dir/subdir/name/" },
{ "/some/dir/subdir/name", "/some/other" },
// With single-char for name
{ "/some/dir/subdir/a", "" },
{ "/some/dir/subdir/a", "/" },
{ "/some/dir/subdir/a", "/some" },
{ "/some/dir/subdir/a", "/some/" },
{ "/some/dir/subdir/a", "/some/dir" },
{ "/some/dir/subdir/a", "/some/dir/" },
{ "/some/dir/subdir/a", "/some/dir/subdir" },
{ "/some/dir/subdir/a", "/some/dir/subdir/a" },
{ "/some/dir/subdir/a", "/some/dir/subdir/a/" },
{ "/some/dir/subdir/a", "/some/other" },
// Bad input (trailing slash)
{ "/some/dir/subdir/a/", "" },
{ "/some/dir/subdir/a/", "/" },
{ "/some/dir/subdir/a/", "/some" },
{ "/some/dir/subdir/a/", "/some/" },
{ "/some/dir/subdir/a/", "/some/dir" },
{ "/some/dir/subdir/a/", "/some/dir/" },
{ "/some/dir/subdir/a/", "/some/dir/subdir/" },
{ "/some/dir/subdir/a/", "/some/dir/subdir/a" },
{ "/some/dir/subdir/a/", "/some/dir/subdir/a/" },
{ "/some/dir/subdir/a/", "/some/other" },
}
);
}
// Test some copying and deletion
if (args.found("system"))
{
const fileName dirA("dirA");
const fileName dirB("dirB");
const fileName dirC("dirC");
const fileName dirD("dirD");
const fileName lnA("lnA");
const fileName lnB("lnB");
// Purge anything existing
Foam::rmDir(dirA, true);
Foam::rmDir(dirB, true);
Foam::rmDir(dirC, true);
Foam::rmDir(dirD, true);
Foam::rm(lnA);
Foam::rm(lnB);
{
fileName name(dirA/dirB/dirC/"abc");
Foam::mkDir(name.path());
touchFileContent(name); // Create real file
Foam::mkDir(dirB/dirB/dirB/dirB);
Foam::ln("test", dirB/"linkB"); // Create dead link
Foam::mkDir(dirC);
Foam::ln("../dirD", dirC/"linkC"); // Create real link
Foam::mkDir(dirD);
for (const fileName& d : { dirA, dirB, dirC, dirD })
{
Info<< "Directory: " << d << " = "
<< readDir(d, fileName::UNDEFINED, false, false) << nl;
if (Foam::rmDir(d, false, true))
{
Info<< " Removed empty dir" << nl;
}
else
{
Info<< " Could not remove empty dir" << nl;
}
}
// Force removal before continuing
Foam::rmDir(dirA, true);
Foam::rmDir(dirB, true);
Foam::rmDir(dirC, true);
Foam::rmDir(dirD, true);
}
Info<< nl << "=========================" << nl
<< "Test some copying and deletion" << endl;
Info<< "Creating directory " << dirA << endl;
Foam::mkDir(dirA);
Info<< "Populating with various files" << endl;
for
(
const std::string name
: { "file-1", "file-2", "bad name one", "bad name 2" }
)
{
// Full path, but without any stripping
const fileName file
(
(static_cast<const std::string&>(dirA) + "/" + name),
false
);
Info<<" create: " << file << endl;
touchFileContent(file);
}
const int oldDebug = POSIX::debug;
POSIX::debug = 1;
// Create link and test it
Info<< "Creating softlink " << lnA << endl;
Foam::ln(dirA, lnA);
fileName::Type lnAType = lnA.type(false);
if (lnAType != fileName::SYMLINK)
{
FatalErrorIn("Test-fileName") << "Type of softlink " << lnA
<< " should be " << fileName::SYMLINK
<< " but is " << lnAType << exit(FatalError);
}
fileName::Type dirAType = lnA.type(true);
if (dirAType != fileName::DIRECTORY)
{
FatalErrorIn("Test-fileName") << "Type of what softlink " << lnA
<< " points to should be " << fileName::DIRECTORY
<< " but is " << dirAType << exit(FatalError);
}
// Copy link only
{
Info<< "Copying (non-follow) softlink " << lnA << " to " << lnB
<< endl;
Foam::cp(lnA, lnB, false);
if (lnB.type(false) != fileName::SYMLINK)
{
FatalErrorIn("Test-fileName") << "Type of softlink " << lnB
<< " should be " << fileName::SYMLINK
<< " but is " << lnB.type(false) << exit(FatalError);
}
if (lnB.type(true) != fileName::DIRECTORY)
{
FatalErrorIn("Test-fileName") << "Type of softlink " << lnB
<< " should be " << fileName::DIRECTORY
<< " but is " << lnB.type(true) << exit(FatalError);
}
// Delete (link)
Foam::rm(lnB);
}
// Copy contents of link
{
Info<< "Copying (contents of) softlink " << lnA << " to " << lnB
<< endl;
Foam::cp(lnA, lnB, true);
if (lnB.type(false) != fileName::DIRECTORY)
{
FatalErrorIn("Test-fileName") << "Type of softlink " << lnB
<< " should be " << fileName::DIRECTORY
<< " but is " << lnB.type(false) << exit(FatalError);
}
// Delete (directory, not link)
Foam::rmDir(lnB);
}
POSIX::debug = oldDebug;
// Verify that rmDir works with bad names too
Foam::rmDir(dirA);
Foam::rm(lnA);
}
if (!defaultTests)
{
return 0;
}
DynamicList<word> wrdList
{
"hello",
"hello1",
"hello2",
"hello3",
"hello4.ext"
};
fileName pathName(wrdList);
Info<< "pathName = " << pathName << nl
<< "nameOp = " << nameOp<fileName>()(pathName) << nl
<< "pathName.name() = >" << pathName.name() << "<\n"
<< "pathName.path() = " << pathName.path() << nl
<< "pathName.ext() = >" << pathName.ext() << "<\n"
<< "pathName.stem = >" << pathName.stem() << "<\n";
Info<< "pathName.components() = " << pathName.components() << nl
<< "pathName.component(2) = " << pathName.component(2) << nl
<< endl;
pathName.replace_name("newName.ext");
Info<< "new name = " << pathName << nl;
Info<< "has ext = " << Switch::name(pathName.has_ext()) << nl;
Info<< "has ext('') = " << Switch::name(pathName.has_ext("")) << nl;
Info<< "has ext(foo) = " << Switch::name(pathName.has_ext("foo")) << nl;
Info<< "has ext(ext) = " << Switch::name(pathName.has_ext("ext")) << nl;
pathName.replace_ext("png");
Info<< "new ext = " << pathName << nl;
pathName.replace_ext(""); // Same as remove_ext
Info<< "new ext = " << pathName << nl;
Info<< "has path = " << Switch::name(pathName.has_path()) << nl;
pathName.remove_path();
pathName.removePath(); // second type should be a no-op
Info<< "removed path = " << pathName << nl;
Info<< nl << nl;
// try with different combination
// The final one should emit warnings
for (label start = 0; start <= wrdList.size(); ++start)
{
fileName instance, local;
word name;
fileName path(SubList<word>(wrdList, wrdList.size()-start, start));
fileName path2 = "."/path;
IOobject::fileNameComponents
(
path,
instance,
local,
name
);
Info<< "IOobject::fileNameComponents for " << path << nl
<< " instance = " << instance << nl
<< " local = " << local << nl
<< " name = " << name << endl;
IOobject::fileNameComponents
(
path2,
instance,
local,
name
);
Info<< "IOobject::fileNameComponents for " << path2 << nl
<< " instance = " << instance << nl
<< " local = " << local << nl
<< " name = " << name << endl;
}
// test findEtcFile
Info<< "\n\nfindEtcFile tests:" << nl
<< " controlDict => " << findEtcFile("controlDict") << nl
<< " badName => " << findEtcFile("badName") << endl;
{
Info<< nl << "Expect a FatalError for findEtcFile() with a bad name:"
<< nl;
const bool oldThrowingError = FatalError.throwing(true);
try
{
Info<< " badName(die) => " << flush
<< findEtcFile("<very-badName>", true) << nl
<< endl;
}
catch (const Foam::error& err)
{
Info<< nl << "findEtcFile() Caught FatalError "
<< err << nl << endl;
}
FatalError.throwing(oldThrowingError);
}
Info<< "\nEnd\n" << endl;
return 0;
}
// ************************************************************************* //