<commit_before>module TeamCityFormatter
  class Logger
    def initialize(io)
      @io = io
    end

    def test_suite_started(test_suite_name)
      render_output("##teamcity[testSuiteStarted name='#{teamcity_escape(test_suite_name)}' timestamp='#{timestamp}']")
    end

    def test_suite_finished(test_suite_name)
      render_output("##teamcity[testSuiteFinished name='#{teamcity_escape(test_suite_name)}' timestamp='#{timestamp}']")
    end

    def test_started(test_name)
      render_output("##teamcity[testStarted name='#{teamcity_escape(test_name)}' timestamp='#{timestamp}' captureStandardOutput='true']")
    end

    def test_failed_with_exception(test_name, exception)
      details = format_exception(exception)
      test_failed(test_name, details)
    end

    def test_failed(test_name, details)
      render_output("##teamcity[testFailed name='#{teamcity_escape(test_name)}' timestamp='#{timestamp}' details='#{teamcity_escape(details)}']")
    end

    def test_finished(test_name)
      render_output("##teamcity[testFinished name='#{teamcity_escape(test_name)}' timestamp='#{timestamp}']")
    end

    private

    def render_output(text)
      @io.puts(text)
      @io.flush
    end

    def teamcity_escape(s)
      s.to_s.strip
        .gsub('|', '||')
        .gsub("'", "|'")
        .gsub(']', '|]')
        .gsub("\r", '|r')
        .gsub("\n", '|n')
    end

    def timestamp_short
      now = Time.now
      '%s.%0.3d' % [now.strftime('%H:%M:%S'), (now.usec / 1000)]
    end

    def timestamp
      now = Time.now
      '%s.%0.3d' % [now.strftime('%Y-%m-%dT%H:%M:%S'), (now.usec / 1000)]
    end

    def format_exception(exception)
      lines = ["#{exception.message} (#{exception.class})"] + exception.backtrace
      lines.join("\n")
    end
  end
end<commit_msg>include PID-based flowId to play nice with parallel_cucumber<commit_after>module TeamCityFormatter
  class Logger
    def initialize(io)
      @io = io
    end

    def test_suite_started(test_suite_name)
      render_output("##teamcity[testSuiteStarted flowId='#{flow_id}' name='#{teamcity_escape(test_suite_name)}' timestamp='#{timestamp}']")
    end

    def test_suite_finished(test_suite_name)
      render_output("##teamcity[testSuiteFinished flowId='#{flow_id}' name='#{teamcity_escape(test_suite_name)}' timestamp='#{timestamp}']")
    end

    def test_started(test_name)
      render_output("##teamcity[testStarted flowId='#{flow_id}' name='#{teamcity_escape(test_name)}' timestamp='#{timestamp}' captureStandardOutput='true']")
    end

    def test_failed_with_exception(test_name, exception)
      details = format_exception(exception)
      test_failed(test_name, details)
    end

    def test_failed(test_name, details)
      render_output("##teamcity[testFailed flowId='#{flow_id}' name='#{teamcity_escape(test_name)}' timestamp='#{timestamp}' details='#{teamcity_escape(details)}']")
    end

    def test_finished(test_name)
      render_output("##teamcity[testFinished flowId='#{flow_id}' name='#{teamcity_escape(test_name)}' timestamp='#{timestamp}']")
    end

    private

    def render_output(text)
      @io.puts(text)
      @io.flush
    end

    def teamcity_escape(s)
      s.to_s.strip
        .gsub('|', '||')
        .gsub("'", "|'")
        .gsub(']', '|]')
        .gsub("\r", '|r')
        .gsub("\n", '|n')
    end

    def timestamp_short
      now = Time.now
      '%s.%0.3d' % [now.strftime('%H:%M:%S'), (now.usec / 1000)]
    end

    def timestamp
      now = Time.now
      '%s.%0.3d' % [now.strftime('%Y-%m-%dT%H:%M:%S'), (now.usec / 1000)]
    end

    def format_exception(exception)
      lines = ["#{exception.message} (#{exception.class})"] + exception.backtrace
      lines.join("\n")
    end

    def flow_id
      Process.pid
    end
  end
end
{'fraction_non_alphanumeric': 0.16377040547656663, 'fraction_numerical': 0.00631911532385466, 'mean_word_length': 3.6104368932038833, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, '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': '106504', 'n_tokens_mistral': 1319, 'n_tokens_neox': 1300, 'n_words': 205}