From 65c6dd70f95a687b1691ac0e85af010d86958b24 Mon Sep 17 00:00:00 2001 From: kucaahbe <93769+kucaahbe@users.noreply.github.com> Date: Fri, 19 Jan 2024 16:39:21 +0200 Subject: [PATCH] test: add unit tests for core functionality * refactor Symlink to accept strings on init * docs: improve * ci: use linter --- .github/workflows/build.yml | 3 +- source/cli.d | 8 +- source/config_parser.d | 4 +- source/path.d | 112 +++++++++++++++++++--- source/symlink.d | 185 +++++++++++++++++++++++++++++++----- source/test_file.d | 37 ++++++++ 6 files changed, 304 insertions(+), 45 deletions(-) create mode 100644 source/test_file.d diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1f1fa89..355cbd6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,7 +43,7 @@ jobs: with: compiler: ${{ matrix.dc }} - - name: 'dub build && dub test' + - name: 'dub build/lint/test' run: | dub_configuration=application @@ -63,6 +63,7 @@ jobs: # fi dub build --compiler=$DC --build=release-debug --config=$dub_configuration # --verbose + dub lint --compiler=$DC --config=$dub_configuration # --verbose dub test --compiler=$DC --config=$dub_configuration # --verbose - name: 'Upload binary' diff --git a/source/cli.d b/source/cli.d index 80ee117..e69bb4d 100644 --- a/source/cli.d +++ b/source/cli.d @@ -51,13 +51,7 @@ void cli_install(ref Config app_config) output ~= " -> "; output ~= symlink.source.absolute; } else { - string backup; - if (symlink.destination.exists) { - backup = symlink.backup(); - symlink.link(); - } else { - symlink.link(); - } + string backup = symlink.link(); output ~= " -> "; output ~= symlink.source.absolute; if (backup) diff --git a/source/config_parser.d b/source/config_parser.d index 9633970..03690c0 100644 --- a/source/config_parser.d +++ b/source/config_parser.d @@ -62,8 +62,8 @@ private void processSDLNode(ref SDLNode node, ref Config config) auto values = child_node.values; if (values.length == 2 && values.all!"a.isText") { config.symlinks ~= [Symlink( - Path(values[0].textValue), - Path(values[1].textValue) + values[0].textValue, + values[1].textValue )]; } else { // TODO: replace TODO with ln content: "ln "val1"" diff --git a/source/path.d b/source/path.d index e3a8f59..77f2c09 100644 --- a/source/path.d +++ b/source/path.d @@ -7,12 +7,12 @@ struct Path { /** original (may be relative) path */ string orig; - /** abosulute path of the `orig` */ + /** an _absolute path of the [orig] */ string absolute; - /** creates Path + /** creates [Path] * Params: - * path = `orig` + * path = [orig] */ this(string path) { @@ -20,21 +20,111 @@ struct Path absolute = orig.expandTilde.absolutePath().asNormalizedPath().array; } - /** Returns: true if Path exists in a file system */ - bool exists() + /// _path processing + unittest + { + import std.file; + import std.process; + + environment["HOME"] = "/my/home"; + immutable auto cwd = getcwd(); + immutable auto pathAbsolute = cwd ~ '/' ~ "file"; + + // expands tilde + auto path = Path("~/file"); + assert(path.absolute == "/my/home/file", path.absolute ~ "!=" ~ "/my/home/file"); + + // absolute path + path = Path("file"); + assert(path.absolute == pathAbsolute, path.absolute ~ "!=" ~ pathAbsolute); + + // normalise path + path = Path("./file"); + assert(path.absolute == pathAbsolute, path.absolute ~ "!=" ~ pathAbsolute); + } + + /** Returns: true if [Path] _exists in a file system */ + bool exists() inout { return absolute.exists; } - /** Returns: true if Path is a directory in a file system */ - bool isDir() + /// returns true if [absolute] _exists + unittest { - return absolute.isDir; + import std.file; + import std.process; + import test_file; + + if (".test".exists) ".test".rmdirRecurse; + scope(exit) if (".test".exists) ".test".rmdirRecurse; + + immutable auto file_path = ".test/app/source"; + + const auto path = Path(file_path); + assert(!path.exists); + + TestFile(file_path, "content").create; + + assert(path.exists); + } + + /** Returns: true if [Path] is a directory in a file system */ + bool isDir() inout + { + return exists && absolute.isDir; } - /** Returns: true if Path is a symbolic link in a file system */ - bool isSymlink() + /// returns true if [absolute] exists and is a directory + unittest { - return absolute.isSymlink; + import std.file; + import std.process; + import test_file; + + if (".test".exists) ".test".rmdirRecurse; + scope(exit) if (".test".exists) ".test".rmdirRecurse; + + immutable auto dir_path = ".test/app"; + + const auto path = Path(dir_path); + assert(!path.isDir); + + dir_path.mkdirRecurse; + + assert(path.isDir); + } + + /** Returns: true if [Path] is a symbolic link in a file system */ + bool isSymlink() inout + { + return exists && absolute.isSymlink; + } + + /// returns true if [absolute] exists and is a symbolic link + unittest + { + import std.file; + import std.process; + import test_file; + + if (".test".exists) ".test".rmdirRecurse; + scope(exit) if (".test".exists) ".test".rmdirRecurse; + + immutable auto dir_path = ".test/app"; + + const auto path = Path(dir_path); + assert(!path.isSymlink); + + dir_path.mkdirRecurse; + assert(!path.isSymlink); + + TestFile(dir_path~'/'~"file", "content"); + assert(!path.isSymlink); + + remove(path.absolute); + symlink("other_file_actually_doesnt_exist_but_who_cares", path.absolute); + + assert(path.isSymlink); } } diff --git a/source/symlink.d b/source/symlink.d index 4444f34..be74f70 100644 --- a/source/symlink.d +++ b/source/symlink.d @@ -6,11 +6,42 @@ import path; /** symbolic link */ struct Symlink { - /** source path */ + /** _source file [Path] */ Path source; - /** destination path */ + /** _destination file [Path] */ Path destination; + /** creates new [Symlink] + * Params: + * src = source path (may be relative to current working directory) + * dest = destination path + */ + this(string src, string dest) + { + source = Path(src); + destination = Path(dest); + } + + /// initialisation could be done with strings + unittest + { + import std.file; + + immutable auto srcPath = ".test/app/source"; + immutable auto destPath = ".test/home/destination"; + immutable auto cwd = getcwd(); + + const auto link = Symlink(srcPath, destPath); + + immutable auto sourcePathAbsolute = cwd ~ '/' ~ srcPath; + immutable auto destinationPathAbsolute = cwd ~ '/' ~ destPath; + + assert(link.source.absolute == sourcePathAbsolute, + link.source.absolute ~ "!=" ~ sourcePathAbsolute); + assert(link.destination.absolute == destinationPathAbsolute, + link.destination.absolute ~ "!=" ~ destinationPathAbsolute); + } + /** test symlink correctness * Returns: source == destination */ @@ -19,40 +50,130 @@ struct Symlink { return source.absolute == destinationActual; } - /** creates symbolic link in the file system */ - void link() + /// returns true if symlink exists and false otherwise + unittest { - symlink(source.absolute, destination.absolute); + import std.file; + import test_file; + + if (".test".exists) ".test".rmdirRecurse; + scope(exit) if (".test".exists) ".test".rmdirRecurse; + + auto srcPath = ".test/app/source"; + auto destPath = ".test/home/destination"; + TestFile(srcPath, "content").create; + TestFile(destPath).create; + + auto link = Symlink(srcPath, destPath); + + assert(!link.ok); + link.link(); + assert(link.ok); } - /** creates backup of the destination - * Returns: pach of the backup file if backup was created - * Bugs: the name of the backup file is currently incorrect (must be + /** creates symbolic _link in the file system + * (creates backup of the destination if it exists) + * Returns: path of the backup file if backup was created + * Bugs: the name of the backup file is currently incorrect (must be * `filename.TIMESTAMP.bak`) */ - string backup() + string link() { - auto backup = destination.absolute ~ ".bak"; - try - { - rename(destination.absolute, backup); - } catch (FileException e) - { - import std.stdio: writeln; - writeln("DEBUG:", destination.absolute, " ", backup); - writeln(e.message, " ", e.errno); - backup = "FAILED BACKUP"; - } - return backup; + string backupPath; + + if (destination.exists) + backupPath = backup(); + + symlink(source.absolute, destination.absolute); + + return backupPath; } - /** Returns: actual `Path` of the desctination */ - Path actual() + /// **success case 1**: creates symlink when destination doesn't exist + unittest + { + import std.file; + import std.path; + + if (".test".exists) ".test".rmdirRecurse; + scope(exit) if (".test".exists) ".test".rmdirRecurse; + + auto srcPath = ".test/app/source"; + auto destPath = ".test/home/destination"; + + srcPath.dirName.mkdirRecurse; + destPath.dirName.mkdirRecurse; + + auto link = Symlink(srcPath, destPath); + link.link(); + + assert(link.destination.absolute.isSymlink); + assert(link.destination.absolute.readLink == link.source.absolute); + } + + /** **success case 2**: creates symlink when destination exists (creating backup + * and returning it's path) + */ + unittest + { + import std.file; + import std.path; + import test_file; + + if (".test".exists) ".test".rmdirRecurse; + scope(exit) if (".test".exists) ".test".rmdirRecurse; + + auto srcPath = ".test/app/source"; + auto destPath = ".test/home/destination"; + immutable auto cwd = getcwd(); + + TestFile(srcPath, "src content").create; + TestFile(destPath, "dest content").create; + + auto link = Symlink(srcPath, destPath); + immutable auto backup = link.link(); + + immutable auto destPathBackup = cwd ~ '/' ~ destPath ~ ".bak"; + + assert(link.destination.absolute.isSymlink); + assert(link.destination.absolute.readLink == link.source.absolute); + assert(backup == destPathBackup, backup ~ "!=" ~ destPathBackup); + } + + /** Returns: _actual [Path] of the destination */ + Path actual() const { return Path(destinationActual); } - private string destinationActual() + /// actual path of the symbolic link destination + // TODO: cases to test: + // * destination doesn't exists + // * destination is not a symlink - this is what's done + // * destination is a symlink + unittest + { + import std.file; + import std.path; + import test_file; + + if (".test".exists) ".test".rmdirRecurse; + scope(exit) if (".test".exists) ".test".rmdirRecurse; + + auto srcPath = ".test/app/source"; + auto destPath = ".test/home/destination"; + immutable auto cwd = getcwd(); + + TestFile(srcPath, "src content").create; + TestFile(destPath, "dest content").create; + + const auto link = Symlink(srcPath, destPath); + immutable auto actualPathAbsolute = cwd ~ '/' ~ destPath; + + assert(link.actual.absolute == actualPathAbsolute); + } + + private string destinationActual() const { if (!destination.absolute.exists) { return []; @@ -62,4 +183,20 @@ struct Symlink { return destination.absolute; } } + + private string backup() + { + auto backup = destination.absolute ~ ".bak"; + try + { + rename(destination.absolute, backup); + } catch (FileException e) + { + import std.stdio: writeln; + writeln("DEBUG:", destination.absolute, " ", backup); + writeln(e.message, " ", e.errno); + backup = "FAILED BACKUP"; + } + return backup; + } } diff --git a/source/test_file.d b/source/test_file.d new file mode 100644 index 0000000..74f7809 --- /dev/null +++ b/source/test_file.d @@ -0,0 +1,37 @@ +version(unittest) +{ + import std.path; + import std_file = std.file; + import path; + + struct TestFile + { + Path path; + + this(string src) + { + path = Path(src); + } + + this(string src, string content) + { + this(src); + test_content = content; + } + + ref TestFile create() + { + immutable auto dir = path.absolute.dirName; + + if (!std_file.exists(dir)) std_file.mkdirRecurse(dir); + + if (test_content.length > 0) { + std_file.write(path.absolute, test_content); + } + + return this; + } + + private string test_content; + } +}