diff options
Diffstat (limited to 'spec/scripts/lib/glfm/update_specification_spec.rb')
-rw-r--r-- | spec/scripts/lib/glfm/update_specification_spec.rb | 169 |
1 files changed, 141 insertions, 28 deletions
diff --git a/spec/scripts/lib/glfm/update_specification_spec.rb b/spec/scripts/lib/glfm/update_specification_spec.rb index 9fb671e0016..852b2b580e6 100644 --- a/spec/scripts/lib/glfm/update_specification_spec.rb +++ b/spec/scripts/lib/glfm/update_specification_spec.rb @@ -1,21 +1,53 @@ # frozen_string_literal: true + require 'fast_spec_helper' require_relative '../../../../scripts/lib/glfm/update_specification' - +require_relative '../../../support/helpers/next_instance_of' + +# IMPORTANT NOTE: See https://docs.gitlab.com/ee/development/gitlab_flavored_markdown/specification_guide/#update-specificationrb-script +# for details on the implementation and usage of the `update_specification.rb` script being tested. +# This developers guide contains diagrams and documentation of the script, +# including explanations and examples of all files it reads and writes. +# +# Note that this test is not structured in a traditional way, with multiple examples +# to cover all different scenarios. Instead, the content of the stubbed test fixture +# files are crafted to cover multiple scenarios with in a single example run. +# +# This is because the invocation of the full script is slow, because it executes +# a subshell for processing, which runs a full Rails environment. +# This results in each full run of the script taking between 30-60 seconds. +# The majority of this is spent loading the Rails environment. +# +# However, only the `with generation of spec.html` context is used +# to test this slow sub-process, and it only contains one example. +# +# All other tests currently in the file pass the `skip_spec_html_generation: true` +# flag to `#process`, which skips the slow sub-process. All of these other tests +# should run in sub-second time when the Spring pre-loader is used. This allows +# logic which is not directly related to the slow sub-processes to be TDD'd with a +# very rapid feedback cycle. RSpec.describe Glfm::UpdateSpecification, '#process' do + include NextInstanceOf + subject { described_class.new } let(:ghfm_spec_txt_uri) { described_class::GHFM_SPEC_TXT_URI } + let(:ghfm_spec_txt_uri_parsed) { instance_double(URI::HTTPS, :ghfm_spec_txt_uri_parsed) } let(:ghfm_spec_txt_uri_io) { StringIO.new(ghfm_spec_txt_contents) } - let(:ghfm_spec_txt_path) { described_class::GHFM_SPEC_TXT_PATH } + let(:ghfm_spec_md_path) { described_class::GHFM_SPEC_MD_PATH } let(:ghfm_spec_txt_local_io) { StringIO.new(ghfm_spec_txt_contents) } - let(:glfm_intro_txt_path) { described_class::GLFM_INTRO_TXT_PATH } - let(:glfm_intro_txt_io) { StringIO.new(glfm_intro_txt_contents) } - let(:glfm_examples_txt_path) { described_class::GLFM_EXAMPLES_TXT_PATH } - let(:glfm_examples_txt_io) { StringIO.new(glfm_examples_txt_contents) } + let(:glfm_intro_md_path) { described_class::GLFM_INTRO_MD_PATH } + let(:glfm_intro_md_io) { StringIO.new(glfm_intro_md_contents) } + let(:glfm_official_specification_examples_md_path) { described_class::GLFM_OFFICIAL_SPECIFICATION_EXAMPLES_MD_PATH } + let(:glfm_official_specification_examples_md_io) { StringIO.new(glfm_official_specification_examples_md_contents) } + let(:glfm_internal_extension_examples_md_path) { described_class::GLFM_INTERNAL_EXTENSION_EXAMPLES_MD_PATH } + let(:glfm_internal_extension_examples_md_io) { StringIO.new(glfm_internal_extension_examples_md_contents) } let(:glfm_spec_txt_path) { described_class::GLFM_SPEC_TXT_PATH } let(:glfm_spec_txt_io) { StringIO.new } + let(:glfm_spec_html_path) { described_class::GLFM_SPEC_HTML_PATH } + let(:glfm_spec_html_io) { StringIO.new } + let(:markdown_tempfile_io) { StringIO.new } let(:ghfm_spec_txt_contents) do <<~MARKDOWN @@ -52,7 +84,7 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do MARKDOWN end - let(:glfm_intro_txt_contents) do + let(:glfm_intro_md_contents) do # language=Markdown <<~MARKDOWN # Introduction @@ -63,9 +95,17 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do MARKDOWN end - let(:glfm_examples_txt_contents) do + let(:glfm_official_specification_examples_md_contents) do <<~MARKDOWN - # GitLab-Specific Section with Examples + # Official Specification Section with Examples + + Some examples. + MARKDOWN + end + + let(:glfm_internal_extension_examples_md_contents) do + <<~MARKDOWN + # Internal Extension Section with Examples Some examples. MARKDOWN @@ -73,44 +113,66 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do before do # Mock default ENV var values - allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_TXT').and_return(nil) + allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_MD').and_return(nil) allow(ENV).to receive(:[]).and_call_original # We mock out the URI and local file IO objects with real StringIO, instead of just mock # objects. This gives better and more realistic coverage, while still avoiding # actual network and filesystem I/O during the spec run. - allow(URI).to receive(:open).with(ghfm_spec_txt_uri) { ghfm_spec_txt_uri_io } - allow(File).to receive(:open).with(ghfm_spec_txt_path) { ghfm_spec_txt_local_io } - allow(File).to receive(:open).with(glfm_intro_txt_path) { glfm_intro_txt_io } - allow(File).to receive(:open).with(glfm_examples_txt_path) { glfm_examples_txt_io } + + # input files + allow(URI).to receive(:parse).with(ghfm_spec_txt_uri).and_return(ghfm_spec_txt_uri_parsed) + allow(ghfm_spec_txt_uri_parsed).to receive(:open).and_return(ghfm_spec_txt_uri_io) + allow(File).to receive(:open).with(ghfm_spec_md_path) { ghfm_spec_txt_local_io } + allow(File).to receive(:open).with(glfm_intro_md_path) { glfm_intro_md_io } + allow(File).to receive(:open).with(glfm_official_specification_examples_md_path) do + glfm_official_specification_examples_md_io + end + allow(File).to receive(:open).with(glfm_internal_extension_examples_md_path) do + glfm_internal_extension_examples_md_io + end + + # output files allow(File).to receive(:open).with(glfm_spec_txt_path, 'w') { glfm_spec_txt_io } + allow(File).to receive(:open).with(glfm_spec_html_path, 'w') { glfm_spec_html_io } + + # Allow normal opening of Tempfile files created during script execution. + tempfile_basenames = [ + described_class::MARKDOWN_TEMPFILE_BASENAME[0], + described_class::STATIC_HTML_TEMPFILE_BASENAME[0] + ].join('|') + # NOTE: This approach with a single regex seems to be the only way this can work. If you + # attempt to have multiple `allow...and_call_original` with `any_args`, the mocked + # parameter matching will fail to match the second one. + tempfiles_regex = /(#{tempfile_basenames})/ + allow(File).to receive(:open).with(tempfiles_regex, any_args).and_call_original # Prevent console output when running tests allow(subject).to receive(:output) end describe 'retrieving latest GHFM spec.txt' do - context 'when UPDATE_GHFM_SPEC_TXT is not true (default)' do + context 'when UPDATE_GHFM_SPEC_MD is not true (default)' do it 'does not download' do - expect(URI).not_to receive(:open).with(ghfm_spec_txt_uri) + expect(URI).not_to receive(:parse).with(ghfm_spec_txt_uri) - subject.process + subject.process(skip_spec_html_generation: true) expect(reread_io(ghfm_spec_txt_local_io)).to eq(ghfm_spec_txt_contents) end end - context 'when UPDATE_GHFM_SPEC_TXT is true' do + context 'when UPDATE_GHFM_SPEC_MD is true' do let(:ghfm_spec_txt_local_io) { StringIO.new } before do - allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_TXT').and_return('true') - allow(File).to receive(:open).with(ghfm_spec_txt_path, 'w') { ghfm_spec_txt_local_io } + allow(ENV).to receive(:[]).with('UPDATE_GHFM_SPEC_MD').and_return('true') + allow(File).to receive(:open).with(ghfm_spec_md_path, 'w') { ghfm_spec_txt_local_io } end context 'with success' do it 'downloads and saves' do - subject.process + subject.process(skip_spec_html_generation: true) expect(reread_io(ghfm_spec_txt_local_io)).to eq(ghfm_spec_txt_contents) end @@ -128,7 +190,9 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do end it 'raises an error' do - expect { subject.process }.to raise_error /version mismatch.*expected.*29.*got.*30/i + expect do + subject.process(skip_spec_html_generation: true) + end.to raise_error /version mismatch.*expected.*29.*got.*30/i end end @@ -136,7 +200,7 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do let(:ghfm_spec_txt_contents) { '' } it 'raises an error if lines cannot be read' do - expect { subject.process }.to raise_error /unable to read lines/i + expect { subject.process(skip_spec_html_generation: true) }.to raise_error /unable to read lines/i end end @@ -146,7 +210,7 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do end it 'raises an error if file is blank' do - expect { subject.process }.to raise_error /unable to read string/i + expect { subject.process(skip_spec_html_generation: true) }.to raise_error /unable to read string/i end end end @@ -157,7 +221,7 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do let(:glfm_contents) { reread_io(glfm_spec_txt_io) } before do - subject.process + subject.process(skip_spec_html_generation: true) end it 'replaces the header text with the GitLab version' do @@ -170,14 +234,18 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do it 'replaces the intro section with the GitLab version' do expect(glfm_contents).not_to match(/What is GitHub Flavored Markdown/m) - expect(glfm_contents).to match(/#{Regexp.escape(glfm_intro_txt_contents)}/m) + expect(glfm_contents).to match(/#{Regexp.escape(glfm_intro_md_contents)}/m) end - it 'inserts the GitLab examples sections before the appendix section' do + it 'inserts the GitLab official spec and internal extension examples sections before the appendix section' do expected = <<~MARKDOWN End of last GitHub examples section. - # GitLab-Specific Section with Examples + # Official Specification Section with Examples + + Some examples. + + # Internal Extension Section with Examples Some examples. @@ -189,6 +257,51 @@ RSpec.describe Glfm::UpdateSpecification, '#process' do end end + describe 'writing GLFM spec.html' do + let(:glfm_contents) { reread_io(glfm_spec_html_io) } + + before do + subject.process + end + + it 'renders HTML from spec.txt', :unlimited_max_formatted_output_length do + expected = <<~HTML + <div class="gl-relative markdown-code-block js-markdown-code"> + <pre data-sourcepos="1:1-4:3" class="code highlight js-syntax-highlight language-yaml" lang="yaml" data-lang-params="frontmatter" v-pre="true"><code><span id="LC1" class="line" lang="yaml"><span class="na">title</span><span class="pi">:</span> <span class="s">GitLab Flavored Markdown (GLFM) Spec</span></span> + <span id="LC2" class="line" lang="yaml"><span class="na">version</span><span class="pi">:</span> <span class="s">alpha</span></span></code></pre> + <copy-code></copy-code> + </div> + <h1 data-sourcepos="6:1-6:14" dir="auto"> + <a id="user-content-introduction" class="anchor" href="#introduction" aria-hidden="true"></a>Introduction</h1> + <h2 data-sourcepos="8:1-8:36" dir="auto"> + <a id="user-content-what-is-gitlab-flavored-markdown" class="anchor" href="#what-is-gitlab-flavored-markdown" aria-hidden="true"></a>What is GitLab Flavored Markdown?</h2> + <p data-sourcepos="10:1-10:42" dir="auto">Intro text about GitLab Flavored Markdown.</p> + <h1 data-sourcepos="12:1-12:23" dir="auto"> + <a id="user-content-section-with-examples" class="anchor" href="#section-with-examples" aria-hidden="true"></a>Section with Examples</h1> + <h2 data-sourcepos="14:1-14:9" dir="auto"> + <a id="user-content-strong" class="anchor" href="#strong" aria-hidden="true"></a>Strong</h2> + <div class="gl-relative markdown-code-block js-markdown-code"> + <pre data-sourcepos="16:1-20:32" class="code highlight js-syntax-highlight language-plaintext" lang="plaintext" data-canonical-lang="example" v-pre="true"><code><span id="LC1" class="line" lang="plaintext">__bold__</span> + <span id="LC2" class="line" lang="plaintext">.</span> + <span id="LC3" class="line" lang="plaintext"><p><strong>bold</strong></p></span></code></pre> + <copy-code></copy-code> + </div> + <p data-sourcepos="22:1-22:36" dir="auto">End of last GitHub examples section.</p> + <h1 data-sourcepos="24:1-24:46" dir="auto"> + <a id="user-content-official-specification-section-with-examples" class="anchor" href="#official-specification-section-with-examples" aria-hidden="true"></a>Official Specification Section with Examples</h1> + <p data-sourcepos="26:1-26:14" dir="auto">Some examples.</p> + <h1 data-sourcepos="28:1-28:42" dir="auto"> + <a id="user-content-internal-extension-section-with-examples" class="anchor" href="#internal-extension-section-with-examples" aria-hidden="true"></a>Internal Extension Section with Examples</h1> + <p data-sourcepos="30:1-30:14" dir="auto">Some examples.</p> + + <h1 data-sourcepos="34:1-34:10" dir="auto"> + <a id="user-content-appendix" class="anchor" href="#appendix" aria-hidden="true"></a>Appendix</h1> + <p data-sourcepos="36:1-36:14" dir="auto">Appendix text.</p> + HTML + expect(glfm_contents).to be == expected + end + end + def reread_io(io) # Reset the io StringIO to the beginning position of the buffer io.seek(0) |