<commit_before>require 'time'
require 'ci/reporter/output_capture'

module CI
  module Reporter
    # Basic structure representing the running of a test suite.  Used to time tests and store results.
    class TestSuite < Struct.new(:name, :tests, :time, :failures, :errors, :skipped, :assertions, :timestamp)
      attr_accessor :testcases
      attr_accessor :stdout, :stderr
      def initialize(name)
        super(name.to_s) # RSpec passes a "description" object instead of a string
        @testcases = []
      end

      # Starts timing the test suite.
      def start
        @start = Time.now
        unless ENV['CI_CAPTURE'] == "off"
          @capture_out = OutputCapture.new($stdout) {|io| $stdout = io }
          @capture_err = OutputCapture.new($stderr) {|io| $stderr = io }
          @capture_out.start
          @capture_err.start
        end
      end

      # Finishes timing the test suite.
      def finish
        self.tests = testcases.size
        self.time = Time.now - @start
        self.timestamp = @start.iso8601
        self.failures = testcases.inject(0) {|sum,tc| sum += tc.failures.select{|f| f.failure? }.size }
        self.errors = testcases.inject(0) {|sum,tc| sum += tc.failures.select{|f| f.error? }.size }
        self.skipped = testcases.inject(0) {|sum,tc| sum += (tc.skipped? ? 1 : 0) }
        self.stdout = @capture_out.finish if @capture_out
        self.stderr = @capture_err.finish if @capture_err
      end

      # Creates the xml builder instance used to create the report xml document.
      def create_builder
        require 'builder'
        # :escape_attrs is obsolete in a newer version, but should do no harm
        Builder::XmlMarkup.new(:indent => 2, :escape_attrs => true)
      end

      # Creates an xml string containing the test suite results.
      def to_xml
        builder = create_builder
        # more recent version of Builder doesn't need the escaping
        def builder.trunc!(txt)
          txt.sub(/\n.*/m, '...')
        end
        builder.instruct!
        attrs = {}
        each_pair {|k,v| attrs[k] = builder.trunc!(v.to_s) unless v.nil? || v.to_s.empty? }
        builder.testsuite(attrs) do
          @testcases.each do |tc|
            tc.to_xml(builder)
          end
          builder.tag! "system-out" do
            builder.text!(self.stdout || '' )
          end
          builder.tag! "system-err" do
            builder.text!(self.stderr || '' )
          end
        end
      end
    end

    # Structure used to represent an individual test case.  Used to time the test and store the result.
    class TestCase < Struct.new(:name, :time, :assertions)
      attr_accessor :failures
      attr_accessor :skipped

      def initialize(*args)
        super
        @failures = []
      end

      # Starts timing the test.
      def start
        @start = Time.now
      end

      # Finishes timing the test.
      def finish
        self.time = Time.now - @start
      end

      # Returns non-nil if the test failed.
      def failure?
        !failures.empty? && failures.detect {|f| f.failure? }
      end

      # Returns non-nil if the test had an error.
      def error?
        !failures.empty? && failures.detect {|f| f.error? }
      end

      def skipped?
        skipped
      end

      # Writes xml representing the test result to the provided builder.
      def to_xml(builder)
        attrs = {}
        each_pair {|k,v| attrs[k] = builder.trunc!(v.to_s) unless v.nil? || v.to_s.empty?}
        builder.testcase(attrs) do
          if skipped?
            builder.skipped
          else
            failures.each do |failure|
              tag = failure.error? ? :error : :failure

              builder.tag!(tag, :type => builder.trunc!(failure.name), :message => builder.trunc!(failure.message)) do
                builder.text!(failure.message + " (#{failure.name})\n")
                builder.text!(failure.location)
              end
            end
          end
        end
      end
    end
  end
end<commit_msg>Remove an instance monkeypatch that can just be in a module<commit_after>require 'time'
require 'ci/reporter/output_capture'

module CI
  module Reporter
    module StructureXmlHelpers
      # Struct#to_h is not available in Ruby 1.9
      def attr_hash
        Hash[self.members.zip(self.values)]
      end

      # Removes empty attributes and truncates long attributes.
      def cleaned_attributes
        attr_array = attr_hash
          .reject {|k,v| v.to_s.empty? }
          .map    {|k,v| [k, truncate_at_newline(v)] }
        Hash[attr_array]
      end

      def truncate_at_newline(txt)
        txt.to_s.sub(/\n.*/m, '...')
      end
    end

    # Basic structure representing the running of a test suite.  Used to time tests and store results.
    class TestSuite < Struct.new(:name, :tests, :time, :failures, :errors, :skipped, :assertions, :timestamp)
      include StructureXmlHelpers

      attr_accessor :testcases
      attr_accessor :stdout, :stderr
      def initialize(name)
        super(name.to_s) # RSpec passes a "description" object instead of a string
        @testcases = []
      end

      # Starts timing the test suite.
      def start
        @start = Time.now
        unless ENV['CI_CAPTURE'] == "off"
          @capture_out = OutputCapture.new($stdout) {|io| $stdout = io }
          @capture_err = OutputCapture.new($stderr) {|io| $stderr = io }
          @capture_out.start
          @capture_err.start
        end
      end

      # Finishes timing the test suite.
      def finish
        self.tests = testcases.size
        self.time = Time.now - @start
        self.timestamp = @start.iso8601
        self.failures = testcases.inject(0) {|sum,tc| sum += tc.failures.select{|f| f.failure? }.size }
        self.errors = testcases.inject(0) {|sum,tc| sum += tc.failures.select{|f| f.error? }.size }
        self.skipped = testcases.inject(0) {|sum,tc| sum += (tc.skipped? ? 1 : 0) }
        self.stdout = @capture_out.finish if @capture_out
        self.stderr = @capture_err.finish if @capture_err
      end

      # Creates the xml builder instance used to create the report xml document.
      def create_builder
        require 'builder'
        # :escape_attrs is obsolete in a newer version, but should do no harm
        Builder::XmlMarkup.new(:indent => 2, :escape_attrs => true)
      end

      # Creates an xml string containing the test suite results.
      def to_xml
        builder = create_builder
        # more recent version of Builder doesn't need the escaping
        builder.instruct!
        builder.testsuite(cleaned_attributes) do
          @testcases.each do |tc|
            tc.to_xml(builder)
          end
          builder.tag! "system-out" do
            builder.text!(self.stdout || '' )
          end
          builder.tag! "system-err" do
            builder.text!(self.stderr || '' )
          end
        end
      end
    end

    # Structure used to represent an individual test case.  Used to time the test and store the result.
    class TestCase < Struct.new(:name, :time, :assertions)
      include StructureXmlHelpers

      attr_accessor :failures
      attr_accessor :skipped

      def initialize(*args)
        super
        @failures = []
      end

      # Starts timing the test.
      def start
        @start = Time.now
      end

      # Finishes timing the test.
      def finish
        self.time = Time.now - @start
      end

      # Returns non-nil if the test failed.
      def failure?
        !failures.empty? && failures.detect {|f| f.failure? }
      end

      # Returns non-nil if the test had an error.
      def error?
        !failures.empty? && failures.detect {|f| f.error? }
      end

      def skipped?
        skipped
      end

      # Writes xml representing the test result to the provided builder.
      def to_xml(builder)
        builder.testcase(cleaned_attributes) do
          if skipped?
            builder.skipped
          else
            failures.each do |failure|
              tag = failure.error? ? :error : :failure

              builder.tag!(tag, type: truncate_at_newline(failure.name), message: truncate_at_newline(failure.message)) do
                builder.text!(failure.message + " (#{failure.name})\n")
                builder.text!(failure.location)
              end
            end
          end
        end
      end
    end
  end
end
{'fraction_non_alphanumeric': 0.09910873440285205, 'fraction_numerical': 0.00261437908496732, 'mean_word_length': 1.973851590106007, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 14}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5499173', 'n_tokens_mistral': 2493, 'n_tokens_neox': 2426, 'n_words': 717}