From 5021972318b3971793738a77e84e442d3b85499c Mon Sep 17 00:00:00 2001
From: Johan Holmberg <holmberg556@gmail.com>
Date: Mon, 17 Aug 2015 23:03:42 +0200
Subject: [PATCH] first version using parallel(1)

to implement -jN option to cmdtest.
may need some cleanup/refactoring.
---
 bin/cmdtest.rb       | 150 +++++++++++++++++++++++++++++++++++--------
 t/CMDTEST_summary.rb |  10 +--
 t/selftest_utils.rb  |   1 +
 3 files changed, 128 insertions(+), 33 deletions(-)

diff --git a/bin/cmdtest.rb b/bin/cmdtest.rb
index ab06890..2d5c320 100755
--- a/bin/cmdtest.rb
+++ b/bin/cmdtest.rb
@@ -288,10 +288,14 @@ module Cmdtest
       if ! @opts.slave
         File.join(tmp_cmdtest_dir, "top")
       else
-        File.join(tmp_cmdtest_dir, @opts.slave)
+        tmp_dir_slave(@opts.slave)
       end
     end
 
+    def tmp_dir_slave(slave_name)
+      File.join(tmp_cmdtest_dir, slave_name)
+    end
+
     def tmp_work_dir
       File.join(tmp_dir, "work")
     end
@@ -319,16 +323,75 @@ module Cmdtest
         end
       end
 
-      clog.notify("testsuite") do
-        for adm_file in @adm_files
-          clog.notify("testfile", adm_file.path) do
+      if @opts.parallel > 1
+        json_files = []
+        nclasses = 0
+        File.open("tmp.sh", "w") do |f|
+          for adm_file in @adm_files
+            if ! @opts.quiet
+              f.puts "echo '### " + "=" * 40 + " " + adm_file.path + "'"
+            end
             for adm_class in adm_file.adm_classes
-              clog.notify("testclass", adm_class.runtime_class.display_name) do
-                for adm_method in adm_class.adm_methods
-                  adm_method.run(clog, self)
-                  if $cmdtest_got_ctrl_c > 0
-                    puts "cmdtest: exiting after Ctrl-C ..."
-                    exit(1)
+              nclasses += 1
+              if ! @opts.quiet
+                f.puts "echo '### " + "-" * 40 + " " + adm_class.as_filename + "'"
+              end
+              for adm_method in adm_class.adm_methods
+                slave_name = adm_method.as_filename
+                f.puts "#{$0} %s --slave %s %s" % [
+                  (@opts.quiet ? "-q" : ""),
+                  slave_name,
+                  adm_file.path,
+                ]
+                json_files << File.join(tmp_dir_slave(slave_name), "result.json")
+              end
+            end
+          end
+        end
+        cmd = "parallel -k -j%d < tmp.sh" % [@opts.parallel]
+        ok = system(cmd)
+        summary = Hash.new(0)
+        for file in json_files
+          File.open(file) do |f|
+            data = JSON.load(f)
+            for k,v in data
+              summary[k] += v
+            end
+          end
+        end
+        summary["classes"] = nclasses
+        if ! @opts.quiet
+          Cmdtest.print_summary(summary)
+        end
+
+        ok = summary["errors"] == 0 && summary["failures"] == 0
+        error_exit = ! @opts.no_exit_code && ! ok
+        exit( error_exit ? 1 : 0 )
+
+
+      elsif @opts.slave
+        for adm_file in @adm_files
+          for adm_class in adm_file.adm_classes
+            for adm_method in adm_class.adm_methods
+              if adm_method.as_filename == @opts.slave
+                adm_method.run(clog, self)
+              end
+            end
+          end
+        end
+
+      else
+        clog.notify("testsuite") do
+          for adm_file in @adm_files
+            clog.notify("testfile", adm_file.path) do
+              for adm_class in adm_file.adm_classes
+                clog.notify("testclass", adm_class.runtime_class.display_name) do
+                  for adm_method in adm_class.adm_methods
+                    adm_method.run(clog, self)
+                    if $cmdtest_got_ctrl_c > 0
+                      puts "cmdtest: exiting after Ctrl-C ..."
+                      exit(1)
+                    end
                   end
                 end
               end
@@ -343,6 +406,21 @@ module Cmdtest
 
   #----------------------------------------------------------------------
 
+  def self.print_summary(summary)
+    puts
+    puts "%s %d test classes, %d test methods, %d commands, %d errors, %d fatals." % [
+      summary["failures"] == 0 && summary["errors"] == 0 ? "###" : "---",
+      summary["classes"],
+      summary["methods"],
+      summary["commands"],
+      summary["failures"],
+      summary["errors"],
+    ]
+    puts
+  end
+
+  #----------------------------------------------------------------------
+
   class ProjectDir
 
     ORIG_CWD = Dir.pwd
@@ -420,7 +498,6 @@ module Cmdtest
   class Main
 
     def initialize
-      _update_cmdtest_level
     end
 
     def _parse_options
@@ -442,6 +519,8 @@ module Cmdtest
     def run
       opts = _parse_options
 
+      _update_cmdtest_level(opts.slave ? 0 : 1)
+
       files = []
       for arg in opts.args
         case
@@ -495,28 +574,43 @@ module Cmdtest
       end
       @runner.run(clog)
 
-      if ! opts.quiet
-        puts
-        puts "%s %d test classes, %d test methods, %d commands, %d errors, %d fatals." % [
-          error_logger.n_failures == 0 && error_logger.n_errors == 0 ? "###" : "---",
-          error_logger.n_classes,
-          error_logger.n_methods,
-          error_logger.n_commands,
-          error_logger.n_failures,
-          error_logger.n_errors
-        ]
-        puts
-      end
+      if opts.slave
+        result = {
+          "classes" => error_logger.n_classes,
+          "methods" => error_logger.n_methods,
+          "commands" => error_logger.n_commands,
+          "failures" => error_logger.n_failures,
+          "errors" => error_logger.n_errors,
+        }
+        result_file = File.join(@runner.tmp_dir, "result.json")
+        File.open(result_file, "w") do |f|
+          f.puts JSON.pretty_generate(result)
+        end
+        exit(0)
+      else
+        if ! opts.quiet
+          puts
+          puts "%s %d test classes, %d test methods, %d commands, %d errors, %d fatals." % [
+            error_logger.n_failures == 0 && error_logger.n_errors == 0 ? "###" : "---",
+            error_logger.n_classes,
+            error_logger.n_methods,
+            error_logger.n_commands,
+            error_logger.n_failures,
+            error_logger.n_errors,
+          ]
+          puts
+        end
 
-      ok = error_logger.everything_ok?
-      error_exit = ! opts.no_exit_code && ! ok
-      exit( error_exit ? 1 : 0 )
+        ok = error_logger.everything_ok?
+        error_exit = ! opts.no_exit_code && ! ok
+        exit( error_exit ? 1 : 0 )
+      end
     end
 
     private
 
-    def _update_cmdtest_level
-      $cmdtest_level = (ENV["CMDTEST_LEVEL"] || "0").to_i + 1
+    def _update_cmdtest_level(inc)
+      $cmdtest_level = (ENV["CMDTEST_LEVEL"] || "0").to_i + inc
       ENV["CMDTEST_LEVEL"] = $cmdtest_level.to_s
     end
 
diff --git a/t/CMDTEST_summary.rb b/t/CMDTEST_summary.rb
index 4ff49aa..e463583 100644
--- a/t/CMDTEST_summary.rb
+++ b/t/CMDTEST_summary.rb
@@ -28,7 +28,7 @@ class CMDTEST_summery < Cmdtest::Testcase
       '      exit_zero',
       '    end',
       '    cmd "true" do',
-      '      exit_nonzero',
+      '      exit_nonzero',     # +1 errors
       '    end',
       '  end',
       '',
@@ -36,25 +36,25 @@ class CMDTEST_summery < Cmdtest::Testcase
       '    cmd "true" do',
       '      exit_zero',
       '    end',
-      '    non_existing_method',
+      '    non_existing_method', # +1 fatals
       '  end',
       '',
       '  def test_foo3',
       '    cmd "true" do',
       '      exit_zero',
       '    end',
-      '    non_existing_method',
+      '    non_existing_method', # +1 fatals
       '  end',
       '',
       '  def test_foo4',
       '    cmd "true" do',
-      '      exit_nonzero',
+      '      exit_nonzero',     # +1 errors
       '    end',
       '  end',
       '',
       '  def test_foo5',
       '    cmd "true" do',
-      '      exit_nonzero',
+      '      exit_nonzero',     # +1 errors
       '    end',
       '  end',
       '',
diff --git a/t/selftest_utils.rb b/t/selftest_utils.rb
index 5e76c90..8b9a008 100644
--- a/t/selftest_utils.rb
+++ b/t/selftest_utils.rb
@@ -12,6 +12,7 @@ module SelftestUtils
   end
 
   def setup
+    ignore_file "**/tmp.sh"
     ignore_file "**/.cmdtest-filter"
     ignore_file "tmp-cmdtest-2/"
     ignore_file "tmp-cmdtest-2/TIMESTAMP"