# Author::    Nicolas Despres  <nicolas.despres@gmail.com>.
# Copyright:: Copyright (c) 2004, 2005 TTK team. All rights reserved.
# License::   LGPL
# $Id: JUnit.rb 567 2005-04-13 08:00:06Z polrop $


#FIXME: adapt me
#FIXME: remove $debug

module TTK

  module Strategies

    # I'm not currently usable, I will be fixed soon.
    class JUnit < Strategy
      include Concrete

      def initialize ( *a, &b )
        super
        @failure = true
        @output = ""
        @result = { }
      end

      def parse_outcome(line)
        case line
        when /^OK \((\d+) test(s|)\)$/
          @result['total tests'] = $1.to_i
          @failure = false
        when /^FAILURES!!!$/
          line = next_line
          if line =~ /^Tests run: (\d+),  Failures: (\d+),  Errors: (\d+)$/ then
            @result['total tests'] = $1.to_i
            @result['total failures'] = $2.to_i
            @result['total errors' ] = $3.to_i
          end
        else
         @result['outcome'] = "not found" if $debug >= 1
        end
        next_line
      end
      protected :parse_outcome

      def parse_location(test_name, line)
        location = []
        i = 0
        while line =~ /^\t(at.+\))$/
          location[i] = $1
          i += 1
          line = next_line
        end
        if location.length != 0 then
          @result[test_name].update({ 'location' => location })
        else
          @result[test_name].update({ 'location' => 'not found' }) if $debug >= 1
        end
        line
      end
      protected :parse_location

      def parse_failures(line)
        if line =~ /^There (was|were) \d+ failure(s|):$/ then
          line = next_line
          while line =~ /(\d)+\) (\w+)\(([\w.]+)\)junit.framework.(\w+): expected:<(.*)> but was:<(.*)>$/
            @result["failure #$1"] = {'name' => $3 + "." + $2,
                                     'reason' => $4,
                                      'expected' => $5,
                                      'was' => $6 }
            line = parse_location("failure #$1", next_line)
          end
        else
          @result['failures'] = "not found" if $debug >= 1
        end
        line
      end
      protected :parse_failures

      def parse_errors(line)
        if line =~ /^There (was|were) \d+ error(s|):$/ then
          line = next_line
          while line =~ /(\d)+\) (\w+)\(([\w.]+)\)([.\w]+)$/
            @result["error #$1"] = {'name' => $3 + "." + $2,
                                        'reason' => $4 }
            line = parse_location("error #$1", next_line)
          end
        else
          @result['errors'] = "not found" if $debug >= 1
        end
        line
      end
      protected :parse_errors

      def parse_time(line)
        if line =~ /^Time: ([\d.]+)$/ then
          @result['time'] = $1.to_f
        else
          @result['time'] = "not found" if $debug >= 1
        end
        next_line
      end
      protected :parse_time

      def assert_line(line, re)
        if not line =~ re
          raise(Failure, "cannot recognized JUnit output '#{line}'.")
        end
        next_line
      end
      protected :assert_line

      def parse_output(line)
        line = assert_line(line, /^[.FE]*$/)
        line = parse_time(line)
        line = parse_errors(line)
        line = parse_failures(line)
        line = assert_line(line, /^$/)
        line = parse_outcome(line)
        line = assert_line(line, /^$/)
      end
      protected :parse_output

      def next_line
        line = @output.gets
        line.chomp unless line.nil?
      end
      protected :next_line

      def check_workdir
        if not File.exist?(@workdir) then
          raise Failure, "the work dir '#{workdir}' doesn't exist."
        elsif not File.directory?(@workdir) then
          raise Failure, "the work dir '#{workdir}' is not a directory."
        elsif not File.readable?(@workdir) then
          raise Failure, "the work dir '#{workdir}' isn unreadable."
        end
      end
      protected :check_workdir

      def check_testclasses_validity
        @testclasses.gsub!(/\.class(\s+|$)/, ' ')
        @testclasses.chomp!(' ')
        testclasses_tbl = @testclasses.split(/\s+/)
        if testclasses_tbl.length == 0 then
          raise Failure, "no testclass are specified."
        end
        testclasses_tbl.each do |x|
          filename = @workdir + "/" + x.gsub(/\./, '/') + ".class"
          if not File.exist?(filename) then
            raise Failure, "the testclass '#{filename}' doesn't exist."
          elsif not File.file?(filename) then
            raise Failure, "the testclass '#{filename}' is not a regular file."
          elsif not File.readable?(filename) then
            raise Failure, "the testclass '#{filename}' is unreadable."
          end
        end
      end
      protected :check_testclasses_validity

      def prologue
        super
        check_workdir
        check_testclasses_validity
        @old_classpath = ENV['CLASSPATH'];
        ENV['CLASSPATH'] = "" if ENV['CLASSPATH'].nil?
        ENV['CLASSPATH'] += ':/usr/share/java/junit.jar:.'
        @old_workdir = Dir.pwd
        Dir.chdir(@workdir)
        @log['real working dir is'] = Dir.pwd if $debug >= 1
      end
      protected :prologue

      def run_impl
        @output = IO.popen("java junit.textui.TestRunner #@testclasses")
        parse_output(next_line)
        @output.close
      end
      protected :run_impl

      def assertion
        @failure ? fail : pass
      end
      protected :assertion

      def epilogue
        ENV['CLASSPATH'] = @old_classpath
        Dir.chdir(@old_workdir)
        print_result
        super
      end
      protected :epilogue

      def print_result
        @result.sort.each { |k,v| @log[k] = v }
      end
      protected :print_result

      attribute :testclasses, 'the name of the JUnit classes to test', "", :mandatory
      attribute :workdir, 'the working directory where java is runned', '.'
      attribute :options, 'extra options to pass to java', ''

    end # class JUnit

  end # module Strategies

end # module TTK