From e58126f56df8e6a4c9f112f36926b957e228b15e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis=20Mos=C4=81ns?= Date: Thu, 5 Mar 2026 21:45:00 +0200 Subject: [PATCH 1/2] Fix output folder for absolute path inputs Currently this fails: ``` $ dstep -o /tmp /usr/include/stdio.h /usr/include/stdlib.h dstep: an unknown error occurred: std.file.FileException@std/file.d(840): /usr/include/stdio.d: Permission denied ``` This is because it tries to create .d files in same directory as input file without respecting output folder. This commit fixes this issue so that it will correctly use output folder for such case. --- dstep/driver/Application.d | 21 +++++-- dstep/driver/Util.d | 86 +++++++++++++++++++++++++++++ tests/functional/Tests.d | 39 +++++++++++++ tests/functional/graph/subfile1.d | 3 + tests/functional/include/subfile3.d | 3 + tests/support/DStepRunner.d | 60 +++++++++++++------- 6 files changed, 187 insertions(+), 25 deletions(-) create mode 100644 tests/functional/graph/subfile1.d create mode 100644 tests/functional/include/subfile3.d diff --git a/dstep/driver/Application.d b/dstep/driver/Application.d index da1c98f7..e9a8d997 100644 --- a/dstep/driver/Application.d +++ b/dstep/driver/Application.d @@ -115,8 +115,9 @@ class Application import std.algorithm; import std.array; import std.range; + import std.path; - import dstep.driver.Util : makeDefaultOutputFile; + import dstep.driver.Util : makeDefaultOutputFile, findBasePath; auto inputFiles = config.inputFiles; @@ -130,11 +131,19 @@ class Application } else { - alias fmap = file => Path.buildPath( - config.output, - makeDefaultOutputFile(file, false)); - - return inputFiles.map!fmap.array; + if (config.output.empty) + { + alias fmap = file => makeDefaultOutputFile(file, false); + return inputFiles.map!fmap.array; + } + else + { + auto basePath = findBasePath(inputFiles); + alias fmap = file => Path.buildPath( + config.output, + makeDefaultOutputFile(relativePath(file, basePath), false)); + return inputFiles.map!fmap.array; + } } } diff --git a/dstep/driver/Util.d b/dstep/driver/Util.d index 90e4d3d6..eceaa693 100644 --- a/dstep/driver/Util.d +++ b/dstep/driver/Util.d @@ -15,3 +15,89 @@ string makeDefaultOutputFile(string inputFile, bool useBaseName = true) return setExtension(inputFile, "d"); } + +string findBasePath(string[] paths) +{ + import std.algorithm.iteration : filter, map, reduce; + import std.algorithm.searching : commonPrefix; + import std.array : array; + import std.path : isAbsolute, dirName, pathSplitter, buildPath; + + auto absolutePaths = paths.filter!isAbsolute.array; + if (absolutePaths.length == 0) + return "."; + + version (Windows) + { + import std.string : replace; + absolutePaths = absolutePaths.map!(p => p.replace("/", "\\")).array; + } + + if (absolutePaths.length == 1) + return dirName(absolutePaths[0]); + + auto parts = absolutePaths + .map!(p => pathSplitter(dirName(p)).array) + .array; + + version (Windows) + { + import std.uni : icmp; + auto common = parts.reduce!((a, b) => commonPrefix!((x, y) => icmp(x, y) == 0)(a, b).array); + } + else + { + auto common = parts.reduce!((a, b) => commonPrefix(a, b).array); + } + + if (common.length == 0) + return "."; + + return buildPath(common); +} + +unittest +{ + assert(findBasePath([]) == "."); + assert(findBasePath(["foo/bar.h", "a/b.h", "c.h"]) == "."); +} + +version (Posix) +{ + unittest + { + assert(findBasePath(["/usr/include/stdio.h"]) == "/usr/include"); + assert(findBasePath(["foo.h", "/usr/include/sys/stat.h"]) == "/usr/include/sys"); + assert(findBasePath(["/usr/include/stdio.h", "/usr/include/sys/stat.h", "/usr/lib/foo.h"]) == "/usr"); + assert(findBasePath(["/usr/include/stdio.h", "/usr/include/string.h", "/usr/include/stdlib.h"]) == "/usr/include"); + assert(findBasePath(["usr.h", "/usr/include/stdio.h", "/usr/include/sys/stat.h", "/usr/include/string.h", "include.h"]) == "/usr/include"); + assert(findBasePath(["/usr/include/stdio.h", "/usr/Include/stdio.h", "/usR/include/stdio.h"]) == "/"); + } +} + +version (Windows) +{ + unittest + { + assert(findBasePath(["/usr/include/stdio.h"]) == "."); + assert(findBasePath(["foo.h", "/usr/include/sys/stat.h"]) == "."); + assert(findBasePath(["/usr/include/stdio.h", "/usr/include/sys/stat.h", "/usr/lib/foo.h"]) == "."); + assert(findBasePath(["/usr/include/stdio.h", "/usr/include/string.h", "/usr/include/stdlib.h"]) == "."); + assert(findBasePath(["usr.h", "/usr/include/stdio.h", "/usr/include/sys/stat.h", "/usr/include/string.h", "include.h"]) == "."); + assert(findBasePath(["/usr/include/stdio.h", "/usr/Include/stdio.h", "/usR/include/stdio.h"]) == "."); + + assert(findBasePath(["C:/usr/include/stdio.h"]) == "C:\\usr\\include"); + assert(findBasePath(["foo.h", "C:/usr/include/sys/stat.h"]) == "C:\\usr\\include\\sys"); + assert(findBasePath(["C:/usr/include/stdio.h", "C:/usr/include/sys/stat.h", "C:/usr/lib/foo.h"]) == "C:\\usr"); + assert(findBasePath(["C:/usr/include/stdio.h", "C:/usr/include/string.h", "C:/usr/include/stdlib.h"]) == "C:\\usr\\include"); + assert(findBasePath(["usr.h", "D:/usr/include/stdio.h", "D:/usr/include/sys/stat.h", "D:/usr/include/string.h", "include.h"]) == "D:\\usr\\include"); + assert(findBasePath(["E:/usr/include/stdio.h", "E:/usr/Include/stdio.h", "E:/usR/include/stdio.h"]) == "E:\\usr\\include"); + + assert(findBasePath(["D:\\test.h"]) == "D:\\"); + assert(findBasePath(["D:\\a\\b.h", "D:\\a\\c.h"]) == "D:\\a"); + assert(findBasePath(["D:\\a\\b.h", "E:\\b.h"]) == "."); + assert(findBasePath(["D:\\B\\a.h", "D:\\b\\c\\D.h", "D:\\B\\c.h"]) == "D:\\B"); + assert(findBasePath(["D:/a\\b/d.h", "D:\\a/B\\c.h"]) == "D:\\a\\b"); + assert(findBasePath(["C:\\a.h", "D:\\b.h"]) == "."); + } +} diff --git a/tests/functional/Tests.d b/tests/functional/Tests.d index 7eebf707..9c255403 100644 --- a/tests/functional/Tests.d +++ b/tests/functional/Tests.d @@ -9,6 +9,7 @@ import tests.support.Assertions; import std.algorithm : canFind; import std.process : executeShell; import std.typecons; +import std.path : absolutePath; void printClangVersion() { @@ -294,6 +295,44 @@ unittest assertIssuesWarning("tests/functional/collision.h"); } +// DStep should output to correct directory when using absolute paths. +unittest +{ + assertRunsDStepCFiles([ + TestFile("tests/functional/graph/subfile1.d", absolutePath("tests/functional/graph/subfile1.h")), + TestFile("tests/functional/include/subfile3.d", absolutePath("tests/functional/include/subfile3.h"))] + ); +} + +unittest +{ + assertRunsDStepCFile( + "tests/functional/functions.d", + absolutePath("tests/functional/functions.h") + ); +} + +// Output in same directory when single file given and no output specified +unittest +{ + assertRunsDStepCFiles( + [TestFile("tests/functional/functions.d", absolutePath("tests/functional/functions.h"))], + ["--unspecified-output"], + false); +} + +// Output in same directory when using multiple files with no output folder specified +unittest +{ + assertRunsDStepCFiles( + [TestFile("tests/functional/aggregate.d", "tests/functional/aggregate.h"), + TestFile("tests/functional/functions.d", "tests/functional/functions.h"), + TestFile("tests/functional/graph/subfile1.d", absolutePath("tests/functional/graph/subfile1.h")), + TestFile("tests/functional/include/subfile3.d", absolutePath("tests/functional/include/subfile3.h"))], + ["--unspecified-output"] + ); +} + version (OSX): // Objective-C tests diff --git a/tests/functional/graph/subfile1.d b/tests/functional/graph/subfile1.d new file mode 100644 index 00000000..673ce4f1 --- /dev/null +++ b/tests/functional/graph/subfile1.d @@ -0,0 +1,3 @@ +extern (C): + +extern __gshared int content; diff --git a/tests/functional/include/subfile3.d b/tests/functional/include/subfile3.d new file mode 100644 index 00000000..ad13ba87 --- /dev/null +++ b/tests/functional/include/subfile3.d @@ -0,0 +1,3 @@ +extern (C): + +void core (int); diff --git a/tests/support/DStepRunner.d b/tests/support/DStepRunner.d index 2974a408..f73d2037 100644 --- a/tests/support/DStepRunner.d +++ b/tests/support/DStepRunner.d @@ -21,13 +21,13 @@ auto testRunDStep( { import core.exception : AssertError; - import std.algorithm : canFind, map; - import std.file : exists, isFile, readText, rmdirRecurse; - import std.path : buildPath; + import std.algorithm : canFind, map, remove; + import std.file : exists, isFile, readText, mkdirRecurse, rmdirRecurse, copy, getcwd; + import std.path : buildPath, isAbsolute, relativePath, dirName; import std.range : join; - import std.array : empty; + import std.array : empty, array; - import dstep.driver.Util : makeDefaultOutputFile; + import dstep.driver.Util : makeDefaultOutputFile, findBasePath; version (OptionalGNUStep) { @@ -48,31 +48,53 @@ auto testRunDStep( string outputDir = namedTempDir("dstepUnitTest"); scope(exit) rmdirRecurse(outputDir); + auto argumentsLength = arguments.length; + arguments = arguments.remove!(x => x == "--unspecified-output"); + bool unspecifiedOutput = arguments.length < argumentsLength; + string[] outputPaths; + string workDir = getcwd(); - if (sourcePaths.length == 1) - { - outputPaths ~= buildPath(outputDir, - makeDefaultOutputFile(sourcePaths[0], false)); - } - else + auto sourceBasePath = findBasePath(sourcePaths); + foreach (ref sourcePath; sourcePaths) { - foreach (sourcePath; sourcePaths) - outputPaths ~= buildPath(outputDir, - makeDefaultOutputFile(sourcePath, false)); + string path = sourcePath; + if (isAbsolute(sourcePath)) + { + path = relativePath(sourcePath, unspecifiedOutput ? workDir : sourceBasePath); + } + if (unspecifiedOutput) + { + auto updatedSourcePath = buildPath(outputDir, path); + mkdirRecurse(dirName(updatedSourcePath)); + copy(sourcePath, updatedSourcePath); + if (isAbsolute(sourcePath)) + { + sourcePath = updatedSourcePath; + } + } + outputPaths ~= buildPath(outputDir, makeDefaultOutputFile(path, false)); } - auto localCommand = ["./bin/dstep"] ~ sourcePaths ~ arguments; + auto dstepPath = buildPath(workDir, "bin", "dstep"); + auto localCommand = [dstepPath] ~ sourcePaths ~ arguments; - if (outputPaths.length == 1) - localCommand ~= ["-o", outputPaths[0]]; + if (unspecifiedOutput) + { + workDir = outputDir; + } else - localCommand ~= ["-o", outputDir]; + { + if (outputPaths.length == 1) + localCommand ~= ["-o", outputPaths[0]]; + else + localCommand ~= ["-o", outputDir]; + } if (command) *command = join(localCommand, " "); - auto result = execute(localCommand); + auto result = execute(localCommand, workDir: workDir); if (outputContents) outputContents.length = outputPaths.length; From 63ee97fe09b9a931d27e2132cfcfa32e0440818f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C4=81vis=20Mos=C4=81ns?= Date: Sun, 28 Jun 2026 17:26:15 +0300 Subject: [PATCH 2/2] Support parsing whole directories --- dstep/Configuration.d | 3 ++ dstep/driver/Application.d | 47 +++++++++++++++++------ dstep/driver/Util.d | 23 +++++++++++- tests/functional/Tests.d | 23 ++++++++++++ tests/support/Assertions.d | 71 ++++++++++++++++++++++++++++++----- tests/support/DStepRunner.d | 74 +++++++++++++++++++++++++++---------- tests/support/Util.d | 6 +++ 7 files changed, 205 insertions(+), 42 deletions(-) diff --git a/dstep/Configuration.d b/dstep/Configuration.d index 3720e54d..d46d2512 100644 --- a/dstep/Configuration.d +++ b/dstep/Configuration.d @@ -37,6 +37,9 @@ struct Configuration /// output file name or folder (in case there are many input files) string output; + /// when true then output to a directory + bool useDirOutput = false; + /// package name @("package", "Use as package name.") string packageName; diff --git a/dstep/driver/Application.d b/dstep/driver/Application.d index e9a8d997..eb0bc1da 100644 --- a/dstep/driver/Application.d +++ b/dstep/driver/Application.d @@ -23,6 +23,7 @@ import dstep.translator.Options; import dstep.core.Exceptions; import dstep.translator.Options; import dstep.translator.Translator; +import dstep.driver.Util : findBasePath, findFiles; class Application { @@ -41,11 +42,11 @@ class Application import std.exception : enforce; import std.range; + this.config = prepareConfig(config); + enforce!DStepException(config.inputFiles.length > 0, "dstep: error: must supply at least one input file\n"); - enforceInputFilesExist(config); - auto translationUnits = makeTranslationUnits(config); enforceTranslationUnitsCompiled(translationUnits); @@ -67,21 +68,45 @@ class Application } } - static void enforceInputFilesExist(const Configuration config) + static Configuration prepareConfig(Configuration config) { + import std.file : exists, isDir, isFile; + import std.path : baseName, asAbsolutePath; import std.exception : enforce; import std.format : format; + import std.array : array; + + Configuration result = config; + string[] files; - foreach (inputFile; config.inputFiles) + // when only one input file is supplied, -o argument is + // interpreted as file path, otherwise as base directory path + result.useDirOutput = config.inputFiles.length != 1; + + foreach (input; config.inputFiles) { enforce!DStepException( - exists(inputFile), - format("dstep: error: file '%s' doesn't exist\n", inputFile)); + exists(input), + format("dstep: error: input '%s' doesn't exist\n", input)); - enforce!DStepException( - isFile(inputFile), - format("dstep: error: '%s' is not a file\n", inputFile)); + if (isDir(input)) + { + files ~= findFiles(input); + result.useDirOutput = true; + } + else + { + enforce!DStepException( + isFile(input), + format("dstep: error: '%s' is not a file\n", input)); + + files ~= input.asAbsolutePath.array; + } } + + result.inputFiles = files; + + return result; } void enforceTranslationUnitsCompiled(TranslationUnit[] translationUnits) @@ -121,9 +146,7 @@ class Application auto inputFiles = config.inputFiles; - // when only one input file is supplied, -o argument is - // interpreted as file path, otherwise as base directory path - if (inputFiles.length == 1) + if (!config.useDirOutput) { return [config.output.empty ? makeDefaultOutputFile(inputFiles.front, false) diff --git a/dstep/driver/Util.d b/dstep/driver/Util.d index eceaa693..49e7371f 100644 --- a/dstep/driver/Util.d +++ b/dstep/driver/Util.d @@ -22,6 +22,7 @@ string findBasePath(string[] paths) import std.algorithm.searching : commonPrefix; import std.array : array; import std.path : isAbsolute, dirName, pathSplitter, buildPath; + import std.file : exists, isDir; auto absolutePaths = paths.filter!isAbsolute.array; if (absolutePaths.length == 0) @@ -33,11 +34,13 @@ string findBasePath(string[] paths) absolutePaths = absolutePaths.map!(p => p.replace("/", "\\")).array; } + auto getDirName = (string path) => (exists(path) && isDir(path)) ? path : dirName(path); + if (absolutePaths.length == 1) - return dirName(absolutePaths[0]); + return getDirName(absolutePaths[0]); auto parts = absolutePaths - .map!(p => pathSplitter(dirName(p)).array) + .map!(p => pathSplitter(getDirName(p)).array) .array; version (Windows) @@ -56,6 +59,20 @@ string findBasePath(string[] paths) return buildPath(common); } +string[] findFiles(string dir, string[] extensions = [".h", ".hpp"]) +{ + import std.file : dirEntries, SpanMode; + import std.path : extension, asAbsolutePath; + import std.algorithm : filter, map, canFind; + import std.array : array; + import std.conv : to; + + return dirEntries(dir, SpanMode.breadth) + .filter!(f => f.isFile && extensions.canFind(extension(f.name))) + .map!(f => f.name.asAbsolutePath.to!string) + .array; +} + unittest { assert(findBasePath([]) == "."); @@ -72,6 +89,8 @@ version (Posix) assert(findBasePath(["/usr/include/stdio.h", "/usr/include/string.h", "/usr/include/stdlib.h"]) == "/usr/include"); assert(findBasePath(["usr.h", "/usr/include/stdio.h", "/usr/include/sys/stat.h", "/usr/include/string.h", "include.h"]) == "/usr/include"); assert(findBasePath(["/usr/include/stdio.h", "/usr/Include/stdio.h", "/usR/include/stdio.h"]) == "/"); + assert(findBasePath(["/usr"]) == "/usr"); + assert(findBasePath(["/usr", "/usr/lib"]) == "/usr"); } } diff --git a/tests/functional/Tests.d b/tests/functional/Tests.d index 9c255403..0a8b30b3 100644 --- a/tests/functional/Tests.d +++ b/tests/functional/Tests.d @@ -206,6 +206,29 @@ unittest ); } +unittest +{ + assertRunsDStepCDir(["tests/functional/module/include.d", + "tests/functional/module/main0.d", + "IGNORE", + "IGNORE", + "tests/functional/module/unused.d"], + "tests/functional/module", + ["--package", "modules", "-Iresources"] + ); +} + +unittest +{ + assertRunsDStepCDir(["tests/functional/module/include.d", + "IGNORE", + "tests/functional/module/main0NotNormalized.d", + "tests/functional/module/main0_public.d"], + "tests/functional/module", + ["--public-submodules", "--package", "modules", "-Iresources", "--unspecified-output"] + ); +} + unittest { assertRunsDStepCFiles([ diff --git a/tests/support/Assertions.d b/tests/support/Assertions.d index c038c216..6a9aa0b4 100644 --- a/tests/support/Assertions.d +++ b/tests/support/Assertions.d @@ -1,6 +1,7 @@ module tests.support.Assertions; import core.exception : AssertError; +import std.sumtype : SumType, match; import tests.support.DStepRunner; @@ -10,6 +11,14 @@ struct TestFile string actual; } +struct TestDir +{ + string[] expected; + string actual; +} + +alias TestInput = SumType!(TestFile[], TestDir[]); + void assertRunsDStepCFile( string expectedPath, string cPath, @@ -30,7 +39,28 @@ void assertRunsDStepCFile( } void assertRunsDStepCFiles( - TestFile[] filesPaths, + TestFile[] input, + string[] arguments = [], + bool strict = false, + string file = __FILE__, + size_t line = __LINE__) +{ + assertRunsDStepCInput(TestInput(input), arguments, strict, file, line); +} + +void assertRunsDStepCDir( + string[] expectedFiles, + string actualDir, + string[] arguments = [], + bool strict = false, + string file = __FILE__, + size_t line = __LINE__) +{ + assertRunsDStepCInput(TestInput([TestDir(expectedFiles, actualDir)]), arguments, strict, file, line); +} + +void assertRunsDStepCInput( + TestInput input, string[] arguments = [], bool strict = false, string file = __FILE__, @@ -39,7 +69,7 @@ void assertRunsDStepCFiles( string[] extended = arguments ~ ["-Iresources"]; assertRunsDStep( - filesPaths, + input, extended, strict, file, @@ -101,13 +131,23 @@ void assertIssuesWarning( } void assertRunsDStep( - TestFile[] testFiles, + TestFile[] input, + string[] arguments, + bool strict, + string file = __FILE__, + size_t line = __LINE__) +{ + assertRunsDStep(TestInput(input), arguments, strict, file, line); +} + +void assertRunsDStep( + TestInput input, string[] arguments, bool strict, string file = __FILE__, size_t line = __LINE__) { - import std.algorithm : map; + import std.algorithm : map, joiner; import std.array : array; import std.file : readText, write; import std.format : format; @@ -122,10 +162,15 @@ void assertRunsDStep( ReturnType!testRunDStep result; + string[] actualInputs = input.match!( + (TestFile[] files) => files.map!(x => x.actual).array, + (TestDir[] dirs) => dirs.map!(x => x.actual).array + ); + try { result = testRunDStep( - testFiles.map!(x => x.actual).array, + actualInputs, arguments, &outputContents, &command, @@ -179,11 +224,19 @@ DStep output: throw new AssertError(message, file, line); } - foreach (index, testFile; testFiles) + string[] expectedOutputs = input.match!( + (TestFile[] files) => files.map!(x => x.expected).array, + (TestDir[] dirs) => dirs.map!(x => x.expected).joiner.array + ); + + foreach (index, expectedPath; expectedOutputs) { - if (fileExists(testFile.expected)) + if (expectedPath == "IGNORE") + continue; + + if (fileExists(expectedPath)) { - string expected = readText(testFile.expected); + string expected = readText(expectedPath); string actual = outputContents[index]; auto mismatch = mismatchRegionTranslated(actual, expected, 8, strict); @@ -211,7 +264,7 @@ DStep output: } else { - write(testFile.expected, outputContents[index]); + write(expectedPath, outputContents[index]); } } } diff --git a/tests/support/DStepRunner.d b/tests/support/DStepRunner.d index f73d2037..841b8a43 100644 --- a/tests/support/DStepRunner.d +++ b/tests/support/DStepRunner.d @@ -11,6 +11,36 @@ version (Windows) private alias TestRunDStepResult = ReturnType!execute; + +void copy(string source, string targetDir) +{ + import std.file : isDir, dirEntries, SpanMode, copy, mkdirRecurse; + import std.path : buildPath, asAbsolutePath, asRelativePath, baseName; + import std.array; + + mkdirRecurse(targetDir); + + if (source.isDir) + { + foreach (entry; dirEntries(source, SpanMode.breadth)) + { + string target = buildPath(targetDir, entry.name.asAbsolutePath.asRelativePath(source.asAbsolutePath).array); + if (entry.isDir) + { + mkdirRecurse(target); + } + else + { + copy(entry.name, target); + } + } + } + else + { + copy(source, buildPath(targetDir, source.baseName)); + } +} + auto testRunDStep( string[] sourcePaths, string[] arguments, @@ -21,13 +51,13 @@ auto testRunDStep( { import core.exception : AssertError; - import std.algorithm : canFind, map, remove; - import std.file : exists, isFile, readText, mkdirRecurse, rmdirRecurse, copy, getcwd; - import std.path : buildPath, isAbsolute, relativePath, dirName; + import std.algorithm : canFind, map, remove, sort; + import std.file : exists, isFile, isDir, readText, rmdirRecurse, getcwd; + import std.path : buildPath, isAbsolute, relativePath, dirName, absolutePath; import std.range : join; import std.array : empty, array; - import dstep.driver.Util : makeDefaultOutputFile, findBasePath; + import dstep.driver.Util : makeDefaultOutputFile, findBasePath, findFiles; version (OptionalGNUStep) { @@ -43,7 +73,7 @@ auto testRunDStep( } foreach (sourcePath; sourcePaths) - assertFileExists(sourcePath, file, line); + assertInputExists(sourcePath, file, line); string outputDir = namedTempDir("dstepUnitTest"); scope(exit) rmdirRecurse(outputDir); @@ -55,25 +85,31 @@ auto testRunDStep( string[] outputPaths; string workDir = getcwd(); - auto sourceBasePath = findBasePath(sourcePaths); + auto sourceBasePath = findBasePath(sourcePaths.map!(file => file.absolutePath).array); + bool dirInput = false; + auto toRelative = (string path) => relativePath(path, unspecifiedOutput ? workDir : sourceBasePath); + foreach (ref sourcePath; sourcePaths) { - string path = sourcePath; - if (isAbsolute(sourcePath)) - { - path = relativePath(sourcePath, unspecifiedOutput ? workDir : sourceBasePath); - } + string path = toRelative(sourcePath.absolutePath); + bool dirPath = isDir(sourcePath); if (unspecifiedOutput) { auto updatedSourcePath = buildPath(outputDir, path); - mkdirRecurse(dirName(updatedSourcePath)); - copy(sourcePath, updatedSourcePath); + copy(sourcePath, dirPath ? updatedSourcePath : dirName(updatedSourcePath)); if (isAbsolute(sourcePath)) { sourcePath = updatedSourcePath; } } - outputPaths ~= buildPath(outputDir, makeDefaultOutputFile(path, false)); + if (dirPath) + { + dirInput = true; + outputPaths ~= findFiles(sourcePath).map!(file => buildPath(outputDir, makeDefaultOutputFile(toRelative(file), false))).array.sort().array; + } else + { + outputPaths ~= buildPath(outputDir, makeDefaultOutputFile(path, false)); + } } auto dstepPath = buildPath(workDir, "bin", "dstep"); @@ -85,7 +121,7 @@ auto testRunDStep( } else { - if (outputPaths.length == 1) + if (outputPaths.length == 1 && !dirInput) localCommand ~= ["-o", outputPaths[0]]; else localCommand ~= ["-o", outputDir]; @@ -152,7 +188,7 @@ class NamedTempDirException : object.Exception } } -void assertFileExists( +void assertInputExists( string expected, string file = __FILE__, size_t line = __LINE__) @@ -161,11 +197,11 @@ void assertFileExists( import std.format : format; - import tests.support.Util : fileExists; + import tests.support.Util : inputExists; - if (!fileExists(expected)) + if (!inputExists(expected)) { - auto message = format("File %s doesn't exist.", expected); + auto message = format("Input %s doesn't exist.", expected); throw new AssertError(message, file, line); } } diff --git a/tests/support/Util.d b/tests/support/Util.d index 4c29fc17..ec62faa3 100644 --- a/tests/support/Util.d +++ b/tests/support/Util.d @@ -6,6 +6,12 @@ bool fileExists(string path) return exists(path) && isFile(path); } +bool inputExists(string path) +{ + import std.file : exists, isFile, isDir; + return exists(path) && (isFile(path) || isDir(path)); +} + string mismatchRegion( string expected, string actual,