ptest: Start of utility to run tests in parallel

The travis config allows multiple tests to run in parallel.  Run a small
program that does the same thing.

Signed-off-by: David Brown <david.brown@linaro.org>
diff --git a/ptest/.gitignore b/ptest/.gitignore
new file mode 100644
index 0000000..9409674
--- /dev/null
+++ b/ptest/.gitignore
@@ -0,0 +1,2 @@
+target
+.*.swp
diff --git a/ptest/Cargo.lock b/ptest/Cargo.lock
new file mode 100644
index 0000000..2783b61
--- /dev/null
+++ b/ptest/Cargo.lock
@@ -0,0 +1,404 @@
+[[package]]
+name = "aho-corasick"
+version = "0.6.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "atty"
+version = "0.2.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)",
+ "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "autocfg"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "backtrace"
+version = "0.3.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "backtrace-sys"
+version = "0.1.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "chrono"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "env_logger"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "failure"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
+ "failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "failure_derive"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "humantime"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "libc"
+version = "0.2.47"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "linked-hash-map"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "log"
+version = "0.4.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "memchr"
+version = "2.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)",
+ "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "num-integer"
+version = "0.1.39"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "num_cpus"
+version = "1.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "0.4.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "ptest"
+version = "0.1.0"
+dependencies = [
+ "chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num_cpus 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "std-semaphore 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "yaml-rust 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "quick-error"
+version = "1.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "quote"
+version = "0.6.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.1.50"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "redox_termios"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex"
+version = "1.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.6.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "rustc-demangle"
+version = "0.1.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "std-semaphore"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "syn"
+version = "0.15.24"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "synstructure"
+version = "0.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "termcolor"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "termion"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "thread_local"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "time"
+version = "0.1.42"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "libc 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)",
+ "redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "ucd-util"
+version = "0.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "unicode-xid"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "utf8-ranges"
+version = "1.0.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "version_check"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi"
+version = "0.3.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "winapi-util"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+
+[[package]]
+name = "wincolor"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[[package]]
+name = "yaml-rust"
+version = "0.4.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+dependencies = [
+ "linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+]
+
+[metadata]
+"checksum aho-corasick 0.6.9 (registry+https://github.com/rust-lang/crates.io-index)" = "1e9a933f4e58658d7b12defcf96dc5c720f20832deebe3e0a19efd3b6aaeeb9e"
+"checksum atty 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "9a7d5b8723950951411ee34d271d99dddcc2035a16ab25310ea2c8cfd4369652"
+"checksum autocfg 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4e5f34df7a019573fb8bdc7e24a2bfebe51a2a1d6bfdbaeccedb3c41fc574727"
+"checksum backtrace 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "b5b493b66e03090ebc4343eb02f94ff944e0cbc9ac6571491d170ba026741eb5"
+"checksum backtrace-sys 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)" = "797c830ac25ccc92a7f8a7b9862bde440715531514594a6154e3d4a54dd769b6"
+"checksum cc 1.0.28 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4a8b715cb4597106ea87c7c84b2f1d452c7492033765df7f32651e66fcf749"
+"checksum cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)" = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4"
+"checksum chrono 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "45912881121cb26fad7c38c17ba7daa18764771836b34fab7d3fbd93ed633878"
+"checksum env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afb070faf94c85d17d50ca44f6ad076bce18ae92f0037d350947240a36e9d42e"
+"checksum failure 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "795bd83d3abeb9220f257e597aa0080a508b27533824adf336529648f6abf7e2"
+"checksum failure_derive 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "ea1063915fd7ef4309e222a5a07cf9c319fb9c7836b1f89b85458672dbb127e1"
+"checksum humantime 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3ca7e5f2e110db35f93b837c81797f3714500b81d517bf20c431b16d3ca4f114"
+"checksum lazy_static 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1"
+"checksum libc 0.2.47 (registry+https://github.com/rust-lang/crates.io-index)" = "48450664a984b25d5b479554c29cc04e3150c97aa4c01da5604a2d4ed9151476"
+"checksum linked-hash-map 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "70fb39025bc7cdd76305867c4eccf2f2dcf6e9a57f5b21a93e1c2d86cd03ec9e"
+"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
+"checksum memchr 2.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "db4c41318937f6e76648f42826b1d9ade5c09cafb5aef7e351240a70f39206e9"
+"checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea"
+"checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1"
+"checksum num_cpus 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5a69d464bdc213aaaff628444e99578ede64e9c854025aa43b9796530afa9238"
+"checksum proc-macro2 0.4.24 (registry+https://github.com/rust-lang/crates.io-index)" = "77619697826f31a02ae974457af0b29b723e5619e113e9397b8b82c6bd253f09"
+"checksum quick-error 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9274b940887ce9addde99c4eee6b5c44cc494b182b97e73dc8ffdcb3397fd3f0"
+"checksum quote 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "53fa22a1994bd0f9372d7a816207d8a2677ad0325b073f5c5332760f0fb62b5c"
+"checksum redox_syscall 0.1.50 (registry+https://github.com/rust-lang/crates.io-index)" = "52ee9a534dc1301776eff45b4fa92d2c39b1d8c3d3357e6eb593e0d795506fc2"
+"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
+"checksum regex 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "37e7cbbd370869ce2e8dff25c7018702d10b21a20ef7135316f8daecd6c25b7f"
+"checksum regex-syntax 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4e47a2ed29da7a9e1960e1639e7a982e6edc6d49be308a3b02daf511504a16d1"
+"checksum rustc-demangle 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "adacaae16d02b6ec37fdc7acfcddf365978de76d1983d3ee22afc260e1ca9619"
+"checksum std-semaphore 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "33ae9eec00137a8eed469fb4148acd9fc6ac8c3f9b110f52cd34698c8b5bfa0e"
+"checksum syn 0.15.24 (registry+https://github.com/rust-lang/crates.io-index)" = "734ecc29cd36e8123850d9bf21dfd62ef8300aaa8f879aabaa899721808be37c"
+"checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015"
+"checksum termcolor 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f"
+"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096"
+"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
+"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
+"checksum ucd-util 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86"
+"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
+"checksum utf8-ranges 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737"
+"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
+"checksum winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0"
+"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+"checksum winapi-util 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "afc5508759c5bf4285e61feb862b6083c8480aec864fa17a81fdec6f69b461ab"
+"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+"checksum wincolor 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "561ed901ae465d6185fa7864d63fbd5720d0ef718366c9a4dc83cf6170d7e9ba"
+"checksum yaml-rust 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "95acf0db5515d07da9965ec0e0ba6cc2d825e2caeb7303b66ca441729801254e"
diff --git a/ptest/Cargo.toml b/ptest/Cargo.toml
new file mode 100644
index 0000000..c63a302
--- /dev/null
+++ b/ptest/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "ptest"
+version = "0.1.0"
+authors = ["david.brown"]
+edition = "2018"
+
+[dependencies]
+chrono = "0.4"
+env_logger = "0.6.0"
+failure = "0.1.5"
+log = "0.4.6"
+num_cpus = "1.9"
+regex = "1.1"
+std-semaphore = "0.1"
+yaml-rust = "0.4"
diff --git a/ptest/src/main.rs b/ptest/src/main.rs
new file mode 100644
index 0000000..1ef4979
--- /dev/null
+++ b/ptest/src/main.rs
@@ -0,0 +1,291 @@
+//! Parallel testing.
+//!
+//! mcuboot simulator is strictly single threaded, as there is a lock around running the C startup
+//! code, because it contains numerous global variables.
+//!
+//! To help speed up testing, the Travis configuration defines all of the configurations that can
+//! be run in parallel.  Fortunately, cargo works well this way, and these can be run by simply
+//! using subprocess for each particular thread.
+
+use chrono::Local;
+use failure::format_err;
+use log::{debug, error, warn};
+use regex::Regex;
+use std::{
+    collections::HashSet,
+    fs::{self, OpenOptions},
+    io::{ErrorKind, stdout, Write},
+    process::{Command, Output},
+    result,
+    sync::{
+        Arc,
+        Mutex,
+    },
+    thread,
+    time::Duration,
+};
+use std_semaphore::Semaphore;
+use yaml_rust::{
+    Yaml,
+    YamlLoader,
+};
+
+type Result<T> = result::Result<T, failure::Error>;
+
+fn main() -> Result<()> {
+    env_logger::init();
+
+    let travis_text = fs::read_to_string("../.travis.yml")?;
+    let travis = YamlLoader::load_from_str(&travis_text)?;
+
+    let ncpus = num_cpus::get();
+    let limiter = Arc::new(Semaphore::new(ncpus as isize));
+
+    let matrix = Matrix::from_yaml(&travis)?;
+
+    let mut children = vec![];
+    let state = State::new(matrix.envs.len());
+    let st2 = state.clone();
+    let _status = thread::spawn(move || {
+        loop {
+            thread::sleep(Duration::new(15, 0));
+            st2.lock().unwrap().status();
+        }
+    });
+    for env in matrix.envs {
+        let state = state.clone();
+        let limiter = limiter.clone();
+
+        let child = thread::spawn(move || {
+            let _run = limiter.access();
+            state.lock().unwrap().start(&env);
+            let out = env.run();
+            state.lock().unwrap().done(&env, out);
+        });
+        children.push(child);
+    }
+
+    for child in children {
+        child.join().unwrap();
+    }
+
+    println!("");
+
+    Ok(())
+}
+
+/// State, for printing status.
+struct State {
+    running: HashSet<String>,
+    done: HashSet<String>,
+    total: usize,
+}
+
+impl State {
+    fn new(total: usize) -> Arc<Mutex<State>> {
+        Arc::new(Mutex::new(State {
+            running: HashSet::new(),
+            done: HashSet::new(),
+            total: total,
+        }))
+    }
+
+    fn start(&mut self, fs: &FeatureSet) {
+        let key = fs.textual();
+        if self.running.contains(&key) || self.done.contains(&key) {
+            warn!("Duplicate: {:?}", key);
+        }
+        debug!("Starting: {} ({} running)", key, self.running.len() + 1);
+        self.running.insert(key);
+        self.status();
+    }
+
+    fn done(&mut self, fs: &FeatureSet, output: Result<Option<Output>>) {
+        let key = fs.textual();
+        self.running.remove(&key);
+        self.done.insert(key.clone());
+        match output {
+            Ok(None) => {
+                // println!("Success {} ({} running)", key, self.running.len());
+            }
+            Ok(Some(output)) => {
+                // Write the output into a file.
+                let mut count = 1;
+                let (mut fd, logname) = loop {
+                    let name = format!("./failure-{:04}.log", count);
+                    count += 1;
+                    match OpenOptions::new()
+                        .create_new(true)
+                        .write(true)
+                        .open(&name)
+                    {
+                        Ok(file) => break (file, name),
+                        Err(ref err) if err.kind() == ErrorKind::AlreadyExists => continue,
+                        Err(err) => {
+                            error!("Unable to write log file to current directory: {:?}", err);
+                            return;
+                        }
+                    }
+                };
+                writeln!(&mut fd, "Test failure {}", key).unwrap();
+                writeln!(&mut fd, "time: {}", Local::now().to_rfc3339()).unwrap();
+                writeln!(&mut fd, "----------------------------------------").unwrap();
+                writeln!(&mut fd, "stdout:").unwrap();
+                fd.write_all(&output.stdout).unwrap();
+                writeln!(&mut fd, "----------------------------------------").unwrap();
+                writeln!(&mut fd, "\nstderr:").unwrap();
+                fd.write_all(&output.stderr).unwrap();
+                error!("Failure {} log:{:?} ({} running)", key, logname,
+                    self.running.len());
+            }
+            Err(err) => {
+                error!("Unable to run test {:?} ({:?})", key, err);
+            }
+        }
+        self.status();
+    }
+
+    fn status(&self) {
+        let running = self.running.len();
+        let done = self.done.len();
+        print!(" {} running ({}/{}/{} done)\r", running, done, running + done, self.total);
+        stdout().flush().unwrap();
+    }
+}
+
+/// The extracted configurations from the travis config
+#[derive(Debug)]
+struct Matrix {
+    envs: Vec<FeatureSet>,
+}
+
+#[derive(Debug, Eq, Hash, PartialEq)]
+struct FeatureSet {
+    // The environment variable to set.
+    env: String,
+    // The successive values to set it to.
+    values: Vec<String>,
+}
+
+impl Matrix {
+    fn from_yaml(yaml: &[Yaml]) -> Result<Matrix> {
+        let mut envs = vec![];
+
+        let mut all_tests = HashSet::new();
+
+        for y in yaml {
+            let m = match lookup_matrix(y) {
+                Some (m) => m,
+                None => continue,
+            };
+            for elt in m {
+                if lookup_os(elt) == Some("linux") {
+                    debug!("yaml: {:?}", lookup_env(elt));
+                    let env = match lookup_env(elt) {
+                        Some (env) => env,
+                        None => continue,
+                    };
+
+                    let fset = FeatureSet::decode(env)?;
+                    debug!("fset: {:?}", fset);
+
+                    if false {
+                        // Respect the groupings in the `.travis.yml` file.
+                        envs.push(fset);
+                    } else {
+                        // Break each test up so we can run more in
+                        // parallel.
+                        let env = fset.env.clone();
+                        for val in fset.values {
+                            if !all_tests.contains(&val) {
+                                all_tests.insert(val.clone());
+                                envs.push(FeatureSet {
+                                    env: env.clone(),
+                                    values: vec![val],
+                                });
+                            } else {
+                                warn!("Duplicate: {:?}: {:?}", env, val);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        Ok(Matrix {
+            envs: envs,
+        })
+    }
+}
+
+impl FeatureSet {
+    fn decode(text: &str) -> Result<FeatureSet> {
+        let re = Regex::new(r#"^([A-Z_]+)="(.*)"$"#)?;
+
+        match re.captures(text) {
+            None => Err(format_err!("Invalid line: {:?}", text)),
+            Some(cap) => {
+                let ename = &cap[1];
+                let sep = if ename == "SINGLE_FEATURES" { ' ' } else { ',' };
+                let values: Vec<_> = cap[2]
+                    .split(sep)
+                    .map(|s| s.to_string())
+                    .collect();
+                debug!("name={:?} values={:?}", ename, values);
+                Ok(FeatureSet {
+                    env: ename.to_string(),
+                    values: values,
+                })
+            }
+        }
+    }
+
+    /// Run a test for this given feature set.  Output is captured and will be returned if there is
+    /// an error.  Each will be run successively, and the first failure will be returned.
+    /// Otherwise, it returns None, which means everything worked.
+    fn run(&self) -> Result<Option<Output>> {
+        for v in &self.values {
+            let output = Command::new("bash")
+               .arg("./scripts/run_tests.sh")
+               .current_dir("..")
+               .env(&self.env, v)
+               .output()?;
+            if !output.status.success() {
+                return Ok(Some(output));
+            }
+        }
+        return Ok(None);
+    }
+
+    /// Convert this feature set into a textual representation
+    fn textual(&self) -> String {
+        use std::fmt::Write;
+
+        let mut buf = String::new();
+
+        write!(&mut buf, "{}:", self.env).unwrap();
+        for v in &self.values {
+            write!(&mut buf, " {}", v).unwrap();
+        }
+
+        buf
+    }
+}
+
+fn lookup_matrix(y: &Yaml) -> Option<&Vec<Yaml>> {
+    let matrix = Yaml::String("matrix".to_string());
+    let include = Yaml::String("include".to_string());
+    y.as_hash()?.get(&matrix)?.as_hash()?.get(&include)?.as_vec()
+}
+
+fn lookup_os(y: &Yaml) -> Option<&str> {
+    let os = Yaml::String("os".to_string());
+
+    y.as_hash()?.get(&os)?.as_str()
+}
+
+fn lookup_env(y: &Yaml) -> Option<&str> {
+    let env = Yaml::String("env".to_string());
+
+    y.as_hash()?.get(&env)?.as_str()
+}