BigW Consortium Gitlab

ansi2html_spec.rb 9.88 KB
Newer Older
1 2
require 'spec_helper'

3
describe Gitlab::Ci::Ansi2html do
4
  subject { described_class }
5 6

  it "prints non-ansi as-is" do
7
    expect(convert_html("Hello")).to eq('Hello')
8 9 10
  end

  it "strips non-color-changing controll sequences" do
11
    expect(convert_html("Hello \e[2Kworld")).to eq('Hello world')
12 13 14
  end

  it "prints simply red" do
15
    expect(convert_html("\e[31mHello\e[0m")).to eq('<span class="term-fg-red">Hello</span>')
16 17 18
  end

  it "prints simply red without trailing reset" do
19
    expect(convert_html("\e[31mHello")).to eq('<span class="term-fg-red">Hello</span>')
20 21 22
  end

  it "prints simply yellow" do
23
    expect(convert_html("\e[33mHello\e[0m")).to eq('<span class="term-fg-yellow">Hello</span>')
24 25 26
  end

  it "prints default on blue" do
27
    expect(convert_html("\e[39;44mHello")).to eq('<span class="term-bg-blue">Hello</span>')
28 29 30
  end

  it "prints red on blue" do
31
    expect(convert_html("\e[31;44mHello")).to eq('<span class="term-fg-red term-bg-blue">Hello</span>')
32 33 34
  end

  it "resets colors after red on blue" do
35
    expect(convert_html("\e[31;44mHello\e[0m world")).to eq('<span class="term-fg-red term-bg-blue">Hello</span> world')
36 37 38
  end

  it "performs color change from red/blue to yellow/blue" do
39
    expect(convert_html("\e[31;44mHello \e[33mworld")).to eq('<span class="term-fg-red term-bg-blue">Hello </span><span class="term-fg-yellow term-bg-blue">world</span>')
40 41 42
  end

  it "performs color change from red/blue to yellow/green" do
43
    expect(convert_html("\e[31;44mHello \e[33;42mworld")).to eq('<span class="term-fg-red term-bg-blue">Hello </span><span class="term-fg-yellow term-bg-green">world</span>')
44 45 46
  end

  it "performs color change from red/blue to reset to yellow/green" do
47
    expect(convert_html("\e[31;44mHello\e[0m \e[33;42mworld")).to eq('<span class="term-fg-red term-bg-blue">Hello</span> <span class="term-fg-yellow term-bg-green">world</span>')
48 49 50
  end

  it "ignores unsupported codes" do
51
    expect(convert_html("\e[51mHello\e[0m")).to eq('Hello')
52 53 54
  end

  it "prints light red" do
55
    expect(convert_html("\e[91mHello\e[0m")).to eq('<span class="term-fg-l-red">Hello</span>')
56 57 58
  end

  it "prints default on light red" do
59
    expect(convert_html("\e[101mHello\e[0m")).to eq('<span class="term-bg-l-red">Hello</span>')
60 61 62
  end

  it "performs color change from red/blue to default/blue" do
63
    expect(convert_html("\e[31;44mHello \e[39mworld")).to eq('<span class="term-fg-red term-bg-blue">Hello </span><span class="term-bg-blue">world</span>')
64 65 66
  end

  it "performs color change from light red/blue to default/blue" do
67
    expect(convert_html("\e[91;44mHello \e[39mworld")).to eq('<span class="term-fg-l-red term-bg-blue">Hello </span><span class="term-bg-blue">world</span>')
68 69 70
  end

  it "prints bold text" do
71
    expect(convert_html("\e[1mHello")).to eq('<span class="term-bold">Hello</span>')
72 73 74
  end

  it "resets bold text" do
75 76
    expect(convert_html("\e[1mHello\e[21m world")).to eq('<span class="term-bold">Hello</span> world')
    expect(convert_html("\e[1mHello\e[22m world")).to eq('<span class="term-bold">Hello</span> world')
77 78 79
  end

  it "prints italic text" do
80
    expect(convert_html("\e[3mHello")).to eq('<span class="term-italic">Hello</span>')
81 82 83
  end

  it "resets italic text" do
84
    expect(convert_html("\e[3mHello\e[23m world")).to eq('<span class="term-italic">Hello</span> world')
85 86 87
  end

  it "prints underlined text" do
88
    expect(convert_html("\e[4mHello")).to eq('<span class="term-underline">Hello</span>')
89 90 91
  end

  it "resets underlined text" do
92
    expect(convert_html("\e[4mHello\e[24m world")).to eq('<span class="term-underline">Hello</span> world')
93 94 95
  end

  it "prints concealed text" do
96
    expect(convert_html("\e[8mHello")).to eq('<span class="term-conceal">Hello</span>')
97 98 99
  end

  it "resets concealed text" do
100
    expect(convert_html("\e[8mHello\e[28m world")).to eq('<span class="term-conceal">Hello</span> world')
101 102 103
  end

  it "prints crossed-out text" do
104
    expect(convert_html("\e[9mHello")).to eq('<span class="term-cross">Hello</span>')
105 106 107
  end

  it "resets crossed-out text" do
108
    expect(convert_html("\e[9mHello\e[29m world")).to eq('<span class="term-cross">Hello</span> world')
109 110 111
  end

  it "can print 256 xterm fg colors" do
112
    expect(convert_html("\e[38;5;16mHello")).to eq('<span class="xterm-fg-16">Hello</span>')
113 114 115
  end

  it "can print 256 xterm fg colors on normal magenta background" do
116
    expect(convert_html("\e[38;5;16;45mHello")).to eq('<span class="xterm-fg-16 term-bg-magenta">Hello</span>')
117 118 119
  end

  it "can print 256 xterm bg colors" do
120
    expect(convert_html("\e[48;5;240mHello")).to eq('<span class="xterm-bg-240">Hello</span>')
121 122
  end

Danny committed
123 124 125 126
  it "can print 256 xterm fg bold colors" do
    expect(convert_html("\e[38;5;16;1mHello")).to eq('<span class="xterm-fg-16 term-bold">Hello</span>')
  end

127
  it "can print 256 xterm bg colors on normal magenta foreground" do
128
    expect(convert_html("\e[48;5;16;35mHello")).to eq('<span class="term-fg-magenta xterm-bg-16">Hello</span>')
129 130 131
  end

  it "prints bold colored text vividly" do
132
    expect(convert_html("\e[1;31mHello\e[0m")).to eq('<span class="term-fg-l-red term-bold">Hello</span>')
133 134 135
  end

  it "prints bold light colored text correctly" do
136
    expect(convert_html("\e[1;91mHello\e[0m")).to eq('<span class="term-fg-l-red term-bold">Hello</span>')
137 138 139
  end

  it "prints &lt;" do
140
    expect(convert_html("<")).to eq('&lt;')
141 142
  end

143
  it "replaces newlines with line break tags" do
144
    expect(convert_html("\n")).to eq('<br>')
145 146 147
  end

  it "groups carriage returns with newlines" do
148
    expect(convert_html("\r\n")).to eq('<br>')
149 150
  end

151 152
  describe "incremental update" do
    shared_examples 'stateable converter' do
153 154 155 156
      let(:pass1_stream) { StringIO.new(pre_text) }
      let(:pass2_stream) { StringIO.new(pre_text + text) }
      let(:pass1) { subject.convert(pass1_stream) }
      let(:pass2) { subject.convert(pass2_stream, pass1.state) }
157 158

      it "to returns html to append" do
159 160 161
        expect(pass2.append).to be_truthy
        expect(pass2.html).to eq(html)
        expect(pass1.html + pass2.html).to eq(pre_html + html)
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
      end
    end

    context "with split word" do
      let(:pre_text) { "\e[1mHello" }
      let(:pre_html) { "<span class=\"term-bold\">Hello</span>" }
      let(:text) { "\e[1mWorld" }
      let(:html) { "<span class=\"term-bold\"></span><span class=\"term-bold\">World</span>" }

      it_behaves_like 'stateable converter'
    end

    context "with split sequence" do
      let(:pre_text) { "\e[1m" }
      let(:pre_html) { "<span class=\"term-bold\"></span>" }
      let(:text) { "Hello" }
      let(:html) { "<span class=\"term-bold\">Hello</span>" }

      it_behaves_like 'stateable converter'
    end

    context "with partial sequence" do
      let(:pre_text) { "Hello\e" }
      let(:pre_html) { "Hello" }
      let(:text) { "[1m World" }
      let(:html) { "<span class=\"term-bold\"> World</span>" }

      it_behaves_like 'stateable converter'
    end
Kamil Trzcinski committed
191 192 193 194 195 196 197 198 199

    context 'with new line' do
      let(:pre_text) { "Hello\r" }
      let(:pre_html) { "Hello\r" }
      let(:text) { "\nWorld" }
      let(:html) { "<br>World" }

      it_behaves_like 'stateable converter'
    end
200
  end
201

202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
  context "with section markers" do
    let(:section_name) { 'test_section' }
    let(:section_start_time) { Time.new(2017, 9, 20).utc }
    let(:section_duration) { 3.seconds }
    let(:section_end_time) { section_start_time + section_duration }
    let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K"}
    let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K"}
    let(:section_start_html) do
      '<div class="hidden" data-action="start"'\
      " data-timestamp=\"#{section_start_time.to_i}\" data-section=\"#{section_name}\">"\
      "#{section_start[0...-5]}</div>"
    end
    let(:section_end_html) do
      '<div class="hidden" data-action="end"'\
      " data-timestamp=\"#{section_end_time.to_i}\" data-section=\"#{section_name}\">"\
      "#{section_end[0...-5]}</div>"
    end

220 221 222 223
    shared_examples 'forbidden char in section_name' do
      it 'ignores sections' do
        text = "#{section_start}Some text#{section_end}"
        html = text.gsub("\033[0K", '').gsub('<', '&lt;')
224

225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271
        expect(convert_html(text)).to eq(html)
      end
    end

    shared_examples 'a legit section' do
      let(:text) { "#{section_start}Some text#{section_end}" }

      it 'prints light red' do
        text = "#{section_start}\e[91mHello\e[0m\n#{section_end}"
        html = %{#{section_start_html}<span class="term-fg-l-red">Hello</span><br>#{section_end_html}}

        expect(convert_html(text)).to eq(html)
      end

      it 'begins with a section_start html marker' do
        expect(convert_html(text)).to start_with(section_start_html)
      end

      it 'ends with a section_end html marker' do
        expect(convert_html(text)).to end_with(section_end_html)
      end
    end

    it_behaves_like 'a legit section'

    context 'section name includes $' do
      let(:section_name) { 'my_$ection'}

      it_behaves_like 'forbidden char in section_name'
    end

    context 'section name includes <' do
      let(:section_name) { '<a_tag>'}

      it_behaves_like 'forbidden char in section_name'
    end

    context 'section name contains .-_' do
      let(:section_name) { 'a.Legit-SeCtIoN_namE' }

      it_behaves_like 'a legit section'
    end

    it 'do not allow XSS injections' do
      text = "#{section_start}section_end:1:2<script>alert('XSS Hack!');</script>#{section_end}"

      expect(convert_html(text)).not_to include('<script>')
272 273 274
    end
  end

275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296
  describe "truncates" do
    let(:text) { "Hello World" }
    let(:stream) { StringIO.new(text) }
    let(:subject) { described_class.convert(stream) }

    before do
      stream.seek(3, IO::SEEK_SET)
    end

    it "returns truncated output" do
      expect(subject.truncated).to be_truthy
    end

    it "does not append output" do
      expect(subject.append).to be_falsey
    end
  end

  def convert_html(data)
    stream = StringIO.new(data)
    subject.convert(stream).html
  end
297
end