aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@gmail.com>2019-05-25 01:48:00 -0400
committerJoseph Hunkeler <jhunkeler@gmail.com>2019-05-25 01:48:00 -0400
commit2ac8f0c609bf52dce1ea2ec6d42182ac9ce982fb (patch)
treecff669945f0711d1d53f5621e54b9186b1b0c174
parent41357ebccf9f6a73b8f11c9d335a0f970e793b32 (diff)
downloaddm-2ac8f0c609bf52dce1ea2ec6d42182ac9ce982fb.tar.gz
Basic integration testing; uses struct instead of asssociativeArray; ensure easyinstall is usable by pip/setup.py
-rw-r--r--source/app.d19
-rw-r--r--source/conda.d69
-rw-r--r--source/merge.d80
-rw-r--r--source/util.d96
4 files changed, 209 insertions, 55 deletions
diff --git a/source/app.d b/source/app.d
index eb7938f..71b8d9e 100644
--- a/source/app.d
+++ b/source/app.d
@@ -15,6 +15,9 @@ int main(string[] args) {
string installer_variant = "3";
string installer_version = "4.5.12";
bool run_tests = false;
+ string test_program = "pytest";
+ string test_args = "-v"; // arguments to pass to test runner
+ string test_requires; // pip requirements file
string mergefile;
string base_spec;
@@ -33,6 +36,9 @@ int main(string[] args) {
"install-variant", "miniconda Python variant", &installer_variant,
"install-version|i", "version of miniconda installer", &installer_version,
"run-tests|R", "scan merged packages and execute their tests", &run_tests,
+ "test-program", "program that will execute tests", &test_program,
+ "test-args", "arguments passed to test executor", &test_args,
+ "test-requires", "path to pip requirements file", &test_requires,
"base-spec", "conda explicit or yaml environment dump file", &base_spec
);
@@ -50,6 +56,10 @@ int main(string[] args) {
output_dir = buildPath(output_dir, env_name).absolutePath;
mergefile = buildPath(mergefile).absolutePath;
+ if (!test_requires.empty) {
+ test_requires = buildPath(test_requires).absolutePath;
+ }
+
if (installer_variant != "3") {
writeln("Python 2.7 has reached end-of-life.");
writeln("3.x variant will be used instead.");
@@ -103,6 +113,15 @@ int main(string[] args) {
conda.dump_env_yaml(buildPath(output_dir, env_name ~ ".yml"));
conda.dump_env_explicit(buildPath(output_dir, env_name ~ ".txt"));
+ if (run_tests) {
+ string testdir = buildPath(output_dir, "testdir");
+ test_runner_t runner = test_runner_t(test_program, test_args, test_requires);
+ testable_t[] testable = testable_packages(conda, mergefile);
+ foreach (t; testable) {
+ integration_test(conda, testdir, runner, t);
+ }
+ }
+
writeln("Done!");
return 0;
}
diff --git a/source/conda.d b/source/conda.d
index 2f447bd..ff44b50 100644
--- a/source/conda.d
+++ b/source/conda.d
@@ -8,48 +8,7 @@ import std.system;
import std.path;
import std.process;
import std.typecons;
-
-
-static auto getenv(string[string] base=null, string preface=null) {
- const char delim = '=';
- char delim_line = '\n';
- string[string] env;
- string cmd = "env";
-
- version (linux) {
- cmd ~= " -0";
- delim_line = '\0';
- }
-
- version (Windows) {
- cmd = "set";
- delim_line = "\r\n";
- }
-
- // Execute a command before dumping the environment
- if (preface !is null) {
- cmd = preface ~ " && " ~ cmd;
- }
-
- auto env_sh = executeShell(cmd, env=base);
- if (env_sh.status) {
- throw new Exception("Unable to read shell environment:" ~ env_sh.output);
- }
-
- foreach (string line; split(env_sh.output, delim_line)) {
- if (line.empty) {
- continue;
- }
- auto data = split(line, delim);
-
- // Recombine extra '=' chars
- if (data.length > 2) {
- data[1] = join(data[1 .. $], delim);
- }
- env[data[0]] = data[1];
- }
- return env;
-}
+import util;
class Conda {
@@ -216,6 +175,7 @@ class Conda {
this.env["PATH"]],
pathSeparator);
this.configure_headless();
+ this.fix_setuptools();
this.initialized = true;
}
@@ -223,6 +183,7 @@ class Conda {
this.env_orig = this.env.dup;
string[string] env_new = getenv(this.env, "source activate " ~ name);
this.env = env_new.dup;
+ this.fix_setuptools();
}
void deactivate() {
@@ -240,7 +201,7 @@ class Conda {
}
int sh(string command) {
- writeln("Running: " ~ command);
+ banner('#', command);
auto proc = spawnShell(command, env=this.env);
scope(exit) wait(proc);
return wait(proc);
@@ -275,6 +236,28 @@ class Conda {
return buildPath(this.install_prefix, "envs", name).exists;
}
+ string env_where(string name) {
+ if (this.env_exists(name)) {
+ return buildPath(this.install_prefix, "envs", name);
+ }
+ return null;
+ }
+
+ string env_current() {
+ return this.env.get("CONDA_PREFIX", null);
+ }
+
+ string site() {
+ return this.sh_block("python -c 'import site; print(site.getsitepackages()[0])'").output.strip;
+ }
+
+ void fix_setuptools() {
+ string pthfile = buildPath(this.site(), "easy-install.pth");
+ if (!pthfile.exists) {
+ File(pthfile, "w+").write("");
+ }
+ }
+
string dump_env_yaml(string filename=null) {
string args;
if (filename !is null) {
diff --git a/source/merge.d b/source/merge.d
index af49fa7..f63a5da 100644
--- a/source/merge.d
+++ b/source/merge.d
@@ -11,6 +11,7 @@ import std.regex;
import std.stdio;
import std.string;
import conda;
+import util;
import dyaml : dumper, Loader, Node;
@@ -20,17 +21,15 @@ auto RE_DMFILE_INVALID_VERSION = regex(r"[ !@#$%^&\*\(\)\-_]+");
auto RE_DELIVERY_NAME = regex(r"(?P<name>.*)[-_](?P<version>.*)[-_]py(?P<python_version>\d+)[-_.](?P<iteration>\d+)[-_.](?P<ext>.*)");
-string safe_spec(string s) {
- return "'" ~ s ~ "'";
+struct test_runner_t {
+ string program;
+ string args;
+ string requires;
}
-
-string safe_install(string[] specs) {
- string[] result;
- foreach (record; specs) {
- result ~= safe_spec(record);
- }
- return result.join(" ");
+struct testable_t {
+ string repo;
+ string head;
}
@@ -87,8 +86,8 @@ bool env_combine(ref Conda conda, string name, string specfile, string mergefile
}
-string[string][] testable_packages(ref Conda conda, string mergefile) {
- string[string][] results;
+testable_t[] testable_packages(ref Conda conda, string mergefile) {
+ testable_t[] results;
foreach (record; dmfile(mergefile)) {
Node meta;
string pkg_d;
@@ -139,7 +138,64 @@ string[string][] testable_packages(ref Conda conda, string mergefile) {
repository = "";
}
- results ~= ["repo": repository, "commit": head];
+ results ~= testable_t(repository, head);
}
return results;
}
+
+auto integration_test(ref Conda conda, string outdir, test_runner_t runner, testable_t pkg) {
+ import core.stdc.stdlib : exit;
+ import std.ascii : letters;
+ import std.conv : to;
+ import std.random : randomSample;
+ import std.utf : byCodeUnit;
+
+ string cwd = getcwd().absolutePath;
+ scope (exit) cwd.chdir;
+ string repo_root = buildPath(outdir, pkg.repo.baseName)
+ .replace(".git", "");
+ outdir.mkdirRecurse;
+
+ if (!repo_root.exists) {
+ if (conda.sh("git clone --recursive " ~ pkg.repo ~ " " ~ repo_root)) {
+ exit(1);
+ }
+ }
+
+ repo_root.chdir;
+
+ if (conda.sh("git checkout " ~ pkg.head)) {
+ exit(1);
+ }
+
+ foreach (string found; conda.scan_packages(repo_root.baseName ~ "*")) {
+ string[] tmp = found.split("-");
+ found = tmp[0];
+ // Does not need to succeed for all matches
+ conda.run("uninstall " ~ found);
+ }
+
+ if (runner.requires) {
+ if (conda.sh("python -m pip install -r " ~ runner.requires)) {
+ exit(1);
+ }
+ }
+
+ if (conda.sh("python -m pip install .[test]")) {
+ exit(1);
+ }
+
+ if (conda.sh("python setup.py egg_info")) {
+ exit(1);
+ }
+
+ auto id = letters.byCodeUnit.randomSample(6).to!string;
+ string basetemp = tempDir.buildPath("dm_testable" ~ id);
+ basetemp.mkdir;
+ scope(exit) basetemp.rmdirRecurse;
+
+ if (conda.sh(runner.program ~ " " ~ runner.args ~ " --basetemp=" ~ basetemp) > 1) {
+ exit(1);
+ }
+ return 0;
+}
diff --git a/source/util.d b/source/util.d
new file mode 100644
index 0000000..d49f9d7
--- /dev/null
+++ b/source/util.d
@@ -0,0 +1,96 @@
+module util;
+import std.stdio;
+import std.string;
+import std.process;
+
+
+enum byte MAXCOLS = 80;
+
+
+void banner(const char ch, string s) {
+ string ruler;
+ byte i = 0;
+ while (i < MAXCOLS) {
+ ruler ~= ch;
+ i++;
+ }
+
+ string result;
+ string[] tmpstr = splitLines(wrap(s, MAXCOLS - 2, ch ~ " ", ch ~ " "));
+ foreach (idx, line; tmpstr) {
+ if (idx < tmpstr.length - 1) {
+ line ~= " \\";
+ }
+ result ~= line ~ "\n";
+ }
+
+ writeln(ruler);
+ write(result);
+ writeln(ruler);
+}
+
+
+static auto getenv(string[string] base=null, string preface=null) {
+ const char delim = '=';
+ char delim_line = '\n';
+ string[string] env;
+ string cmd = "env";
+
+ version (linux) {
+ cmd ~= " -0";
+ delim_line = '\0';
+ }
+
+ version (Windows) {
+ cmd = "set";
+ delim_line = "\r\n";
+ }
+
+ // Execute a command before dumping the environment
+ if (preface !is null) {
+ cmd = preface ~ " && " ~ cmd;
+ }
+
+ auto env_sh = executeShell(cmd, env=base);
+ if (env_sh.status) {
+ throw new Exception("Unable to read shell environment:" ~ env_sh.output);
+ }
+
+ foreach (string line; split(env_sh.output, delim_line)) {
+ if (line.empty) {
+ continue;
+ }
+ auto data = split(line, delim);
+
+ // Recombine extra '=' chars
+ if (data.length > 2) {
+ data[1] = join(data[1 .. $], delim);
+ }
+ env[data[0]] = data[1];
+ }
+ return env;
+}
+
+
+string safe_spec(string s) {
+ return "'" ~ s ~ "'";
+}
+
+
+string safe_install(string[] specs) {
+ string[] result;
+ foreach (record; specs) {
+ result ~= safe_spec(record);
+ }
+ return result.join(" ");
+}
+
+
+string safe_install(string specs) {
+ string[] result;
+ foreach (record; specs.split(" ")) {
+ result ~= safe_spec(record);
+ }
+ return result.join(" ");
+}
+