popen

in spec/lib/gitlab/diff/file_spec.rb [296:880]


           def popen(cmd, path=nil)
             unless cmd.is_a?(Array)
        -      raise "System commands must be given as an array of strings"
        +      raise RuntimeError, "System commands must be given as an array of strings"
        +      
             end
      EOS
    end

    describe '#added_lines' do
      context 'when stats argument given' do
        let(:stats) { double(Gitaly::DiffStats, additions: 10, deletions: 15) }

        it 'returns added lines from stats' do
          expect(diff_file.added_lines).to eq(stats.additions)
        end
      end

      context 'when stats argument not given' do
        let(:stats) { nil }

        it 'returns added lines by parsing raw diff' do
          allow(diff_file).to receive(:raw_diff) { raw_diff }

          expect(diff_file.added_lines).to eq(2)
        end
      end
    end

    describe '#removed_lines' do
      context 'when stats argument given' do
        let(:stats) { double(Gitaly::DiffStats, additions: 10, deletions: 15) }

        it 'returns removed lines from stats' do
          expect(diff_file.removed_lines).to eq(stats.deletions)
        end
      end

      context 'when stats argument not given' do
        let(:stats) { nil }

        it 'returns removed lines by parsing raw diff' do
          allow(diff_file).to receive(:raw_diff) { raw_diff }

          expect(diff_file.removed_lines).to eq(1)
        end
      end
    end
  end

  describe '#simple_viewer' do
    context 'when the file is not diffable' do
      before do
        allow(diff_file).to receive(:diffable?).and_return(false)
      end

      it 'returns a Not Diffable viewer' do
        expect(diff_file.simple_viewer).to be_a(DiffViewer::NotDiffable)
      end
    end

    context 'when the content changed' do
      context 'when the file represented by the diff file is binary' do
        before do
          allow(diff_file).to receive(:binary?).and_return(true)
        end

        it 'returns a No Preview viewer' do
          expect(diff_file.simple_viewer).to be_a(DiffViewer::NoPreview)
        end
      end

      context 'when the diff file old and new blob types are different' do
        before do
          allow(diff_file).to receive(:different_type?).and_return(true)
        end

        it 'returns a No Preview viewer' do
          expect(diff_file.simple_viewer).to be_a(DiffViewer::NoPreview)
        end
      end

      context 'when the file represented by the diff file is text-based' do
        it 'returns a text viewer' do
          expect(diff_file.simple_viewer).to be_a(DiffViewer::Text)
        end
      end
    end

    context 'when created' do
      let(:commit) { project.commit('913c66a37b4a45b9769037c55c2d238bd0942d2e') }
      let(:diff_file) { commit.diffs.diff_file_with_new_path('files/ruby/popen.rb') }

      before do
        allow(diff_file).to receive(:content_changed?).and_return(nil)
      end

      context 'when the file represented by the diff file is binary' do
        before do
          allow(diff_file).to receive(:binary?).and_return(true)
        end

        it 'returns an Added viewer' do
          expect(diff_file.simple_viewer).to be_a(DiffViewer::Added)
        end
      end

      context 'when the diff file old and new blob types are different' do
        before do
          allow(diff_file).to receive(:different_type?).and_return(true)
        end

        it 'returns an Added viewer' do
          expect(diff_file.simple_viewer).to be_a(DiffViewer::Added)
        end
      end

      context 'when the file represented by the diff file is text-based' do
        it 'returns a text viewer' do
          expect(diff_file.simple_viewer).to be_a(DiffViewer::Text)
        end
      end
    end

    context 'when deleted' do
      let(:commit) { project.commit('d59c60028b053793cecfb4022de34602e1a9218e') }
      let(:diff_file) { commit.diffs.diff_file_with_old_path('files/js/commit.js.coffee') }

      before do
        allow(diff_file).to receive(:content_changed?).and_return(nil)
      end

      context 'when the file represented by the diff file is binary' do
        before do
          allow(diff_file).to receive(:binary?).and_return(true)
        end

        it 'returns a Deleted viewer' do
          expect(diff_file.simple_viewer).to be_a(DiffViewer::Deleted)
        end
      end

      context 'when the diff file old and new blob types are different' do
        before do
          allow(diff_file).to receive(:different_type?).and_return(true)
        end

        it 'returns a Deleted viewer' do
          expect(diff_file.simple_viewer).to be_a(DiffViewer::Deleted)
        end
      end

      context 'when the file represented by the diff file is text-based' do
        it 'returns a text viewer' do
          expect(diff_file.simple_viewer).to be_a(DiffViewer::Text)
        end
      end
    end

    context 'when renamed' do
      let(:commit) { project.commit('6907208d755b60ebeacb2e9dfea74c92c3449a1f') }
      let(:diff_file) { commit.diffs.diff_file_with_new_path('files/js/commit.coffee') }

      before do
        allow(diff_file).to receive(:content_changed?).and_return(nil)
      end

      it 'returns a Renamed viewer' do
        expect(diff_file.simple_viewer).to be_a(DiffViewer::Renamed)
      end
    end

    context 'when mode changed' do
      before do
        allow(diff_file).to receive(:content_changed?).and_return(nil)
        allow(diff_file).to receive(:mode_changed?).and_return(true)
      end

      it 'returns a Mode Changed viewer' do
        expect(diff_file.simple_viewer).to be_a(DiffViewer::ModeChanged)
      end
    end

    context 'when no other conditions apply' do
      before do
        allow(diff_file).to receive(:content_changed?).and_return(false)
        allow(diff_file).to receive(:new_file?).and_return(false)
        allow(diff_file).to receive(:deleted_file?).and_return(false)
        allow(diff_file).to receive(:renamed_file?).and_return(false)
        allow(diff_file).to receive(:mode_changed?).and_return(false)
        allow(diff_file).to receive(:text?).and_return(false)
      end

      it 'returns a No Preview viewer' do
        expect(diff_file.simple_viewer).to be_a(DiffViewer::NoPreview)
      end
    end
  end

  describe '#rich_viewer' do
    let(:commit) { project.commit('2f63565e7aac07bcdadb654e253078b727143ec4') }
    let(:diff_file) { commit.diffs.diff_file_with_new_path('files/images/6049019_460s.jpg') }

    context 'when the diff file has a matching viewer' do
      context 'when the diff file content did not change' do
        before do
          allow(diff_file).to receive(:content_changed?).and_return(false)
        end

        it 'returns nil' do
          expect(diff_file.rich_viewer).to be_nil
        end
      end

      context 'when the diff file is not diffable' do
        before do
          allow(diff_file).to receive(:diffable?).and_return(false)
        end

        it 'returns nil' do
          expect(diff_file.rich_viewer).to be_nil
        end
      end

      context 'when the diff file old and new blob types are different' do
        before do
          allow(diff_file).to receive(:different_type?).and_return(true)
        end

        it 'returns nil' do
          expect(diff_file.rich_viewer).to be_nil
        end
      end

      context 'when the diff file has an external storage error' do
        before do
          allow(diff_file).to receive(:external_storage_error?).and_return(true)
        end

        it 'returns nil' do
          expect(diff_file.rich_viewer).to be_nil
        end
      end

      context 'when everything is right' do
        it 'returns the viewer' do
          expect(diff_file.rich_viewer).to be_a(DiffViewer::Image)
        end
      end
    end

    context 'when the diff file does not have a matching viewer' do
      let(:commit) { project.commit('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') }
      let(:diff_file) { commit.diffs.diff_file_with_new_path('files/ruby/popen.rb') }

      it 'returns nil' do
        expect(diff_file.rich_viewer).to be_nil
      end
    end
  end

  describe '#rendered_as_text?' do
    context 'when the simple viewer is text-based' do
      let(:commit) { project.commit('570e7b2abdd848b95f2f578043fc23bd6f6fd24d') }
      let(:diff_file) { commit.diffs.diff_file_with_new_path('files/ruby/popen.rb') }

      context 'when ignoring errors' do
        context 'when the viewer has render errors' do
          before do
            diff_file.diff.too_large!
          end

          it 'returns true' do
            expect(diff_file.rendered_as_text?).to be_truthy
          end
        end

        context "when the viewer doesn't have render errors" do
          it 'returns true' do
            expect(diff_file.rendered_as_text?).to be_truthy
          end
        end
      end

      context 'when not ignoring errors' do
        context 'when the viewer has render errors' do
          before do
            diff_file.diff.too_large!
          end

          it 'returns false' do
            expect(diff_file.rendered_as_text?(ignore_errors: false)).to be_falsey
          end
        end

        context "when the viewer doesn't have render errors" do
          it 'returns true' do
            expect(diff_file.rendered_as_text?(ignore_errors: false)).to be_truthy
          end
        end
      end
    end

    context 'when the simple viewer is binary' do
      let(:commit) { project.commit('2f63565e7aac07bcdadb654e253078b727143ec4') }
      let(:diff_file) { commit.diffs.diff_file_with_new_path('files/images/6049019_460s.jpg') }

      it 'returns false' do
        expect(diff_file.rendered_as_text?).to be_falsey
      end
    end
  end

  context 'when neither blob exists' do
    let(:blank_diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: Gitlab::Git::BLANK_SHA, head_sha: Gitlab::Git::BLANK_SHA) }
    let(:diff_file) { described_class.new(diff, diff_refs: blank_diff_refs, repository: project.repository) }

    describe '#blob' do
      it 'returns a concrete nil so it can be used in boolean expressions' do
        actual = diff_file.blob && true

        expect(actual).to be_nil
      end
    end

    describe '#binary?' do
      it 'returns false' do
        expect(diff_file).not_to be_binary
      end
    end

    describe '#size' do
      it 'returns zero' do
        expect(diff_file.size).to be_zero
      end
    end

    describe '#empty?' do
      it 'returns true' do
        expect(diff_file.empty?).to be_truthy
      end
    end

    describe '#different_type?' do
      it 'returns false' do
        expect(diff_file).not_to be_different_type
      end
    end

    describe '#content_changed?' do
      it 'returns false' do
        expect(diff_file).not_to be_content_changed
      end
    end
  end

  describe '#diff_hunk' do
    context 'when first line is a match' do
      let(:raw_diff) do
        <<~EOS
          --- a/files/ruby/popen.rb
          +++ b/files/ruby/popen.rb
          @@ -6,12 +6,18 @@ module Popen

             def popen(cmd, path=nil)
               unless cmd.is_a?(Array)
          -      raise "System commands must be given as an array of strings"
          +      raise RuntimeError, "System commands must be given as an array of strings"
               end
        EOS
      end

      it 'returns raw diff up to given line index' do
        allow(diff_file).to receive(:raw_diff) { raw_diff }
        diff_line = instance_double(Gitlab::Diff::Line, index: 4)

        diff_hunk = <<~EOS
          @@ -6,12 +6,18 @@ module Popen

             def popen(cmd, path=nil)
               unless cmd.is_a?(Array)
          -      raise "System commands must be given as an array of strings"
          +      raise RuntimeError, "System commands must be given as an array of strings"
        EOS

        expect(diff_file.diff_hunk(diff_line)).to eq(diff_hunk.strip)
      end
    end

    context 'when first line is not a match' do
      let(:raw_diff) do
        <<~EOS
          @@ -1,4 +1,4 @@
          -Copyright (c) 2011-2017 GitLab B.V.
          +Copyright (c) 2011-2019 GitLab B.V.

          With regard to the GitLab Software:

          @@ -9,17 +9,21 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
          copies of the Software, and to permit persons to whom the Software is
          furnished to do so, subject to the following conditions:
        EOS
      end

      it 'returns raw diff up to given line index' do
        allow(diff_file).to receive(:raw_diff) { raw_diff }
        diff_line = instance_double(Gitlab::Diff::Line, index: 5)

        diff_hunk = <<~EOS
          -Copyright (c) 2011-2017 GitLab B.V.
          +Copyright (c) 2011-2019 GitLab B.V.

          With regard to the GitLab Software:

          @@ -9,17 +9,21 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
          copies of the Software, and to permit persons to whom the Software is
        EOS

        expect(diff_file.diff_hunk(diff_line)).to eq(diff_hunk.strip)
      end
    end
  end

  describe '#empty?' do
    let(:project) do
      create(:project, :custom_repo, files: {})
    end
    let(:branch_name) { 'master' }

    context 'when empty file is created' do
      it 'returns true' do
        diff_file = create_file('empty.md', '')

        expect(diff_file.empty?).to be_truthy
      end
    end

    context 'when empty file is deleted' do
      it 'returns true' do
        create_file('empty.md', '')
        diff_file = delete_file('empty.md')

        expect(diff_file.empty?).to be_truthy
      end
    end

    context 'when file with content is truncated' do
      it 'returns false' do
        create_file('with-content.md', 'file content')
        diff_file = update_file('with-content.md', '')

        expect(diff_file.empty?).to be_falsey
      end
    end

    context 'when empty file has content added' do
      it 'returns false' do
        create_file('empty.md', '')
        diff_file = update_file('empty.md', 'new content')

        expect(diff_file.empty?).to be_falsey
      end
    end
  end

  describe '#fully_expanded?' do
    let(:project) do
      create(:project, :custom_repo, files: {})
    end
    let(:branch_name) { 'master' }

    context 'when empty file is created' do
      it 'returns true' do
        diff_file = create_file('empty.md', '')

        expect(diff_file.fully_expanded?).to be_truthy
      end
    end

    context 'when empty file is deleted' do
      it 'returns true' do
        create_file('empty.md', '')
        diff_file = delete_file('empty.md')

        expect(diff_file.fully_expanded?).to be_truthy
      end
    end

    context 'when short file with last line removed' do
      it 'returns true' do
        create_file('with-content.md', (1..3).to_a.join("\n"))
        diff_file = update_file('with-content.md', (1..2).to_a.join("\n"))

        expect(diff_file.fully_expanded?).to be_truthy
      end
    end

    context 'when a single line is added to empty file' do
      it 'returns true' do
        create_file('empty.md', '')
        diff_file = update_file('empty.md', 'new content')

        expect(diff_file.fully_expanded?).to be_truthy
      end
    end

    context 'when single line file is changed' do
      it 'returns true' do
        create_file('file.md', 'foo')
        diff_file = update_file('file.md', 'bar')

        expect(diff_file.fully_expanded?).to be_truthy
      end
    end

    context 'when long file is changed' do
      before do
        create_file('file.md', (1..999).to_a.join("\n"))
      end

      context 'when first line is removed' do
        it 'returns true' do
          diff_file = update_file('file.md', (2..999).to_a.join("\n"))

          expect(diff_file.fully_expanded?).to be_falsey
        end
      end

      context 'when last line is removed' do
        it 'returns true' do
          diff_file = update_file('file.md', (1..998).to_a.join("\n"))

          expect(diff_file.fully_expanded?).to be_falsey
        end
      end

      context 'when first and last lines are removed' do
        it 'returns false' do
          diff_file = update_file('file.md', (2..998).to_a.join("\n"))

          expect(diff_file.fully_expanded?).to be_falsey
        end
      end

      context 'when first and last lines are changed' do
        it 'returns false' do
          content = (2..998).to_a
          content.prepend('a')
          content.append('z')
          content = content.join("\n")

          diff_file = update_file('file.md', content)

          expect(diff_file.fully_expanded?).to be_falsey
        end
      end

      context 'when every line are changed' do
        it 'returns true' do
          diff_file = update_file('file.md', "hi\n" * 999)

          expect(diff_file.fully_expanded?).to be_truthy
        end
      end

      context 'when all contents are cleared' do
        it 'returns true' do
          diff_file = update_file('file.md', "")

          expect(diff_file.fully_expanded?).to be_truthy
        end
      end

      context 'when file is binary' do
        it 'returns true' do
          diff_file = update_file('file.md', (1..998).to_a.join("\n"))
          allow(diff_file).to receive(:binary?).and_return(true)

          expect(diff_file.fully_expanded?).to be_truthy
        end
      end
    end
  end
end