2022-07-16 23:28:13 +05:30
# frozen_string_literal: true
2022-11-25 23:54:43 +05:30
2022-07-16 23:28:13 +05:30
require 'fast_spec_helper'
require_relative '../../../../scripts/lib/glfm/update_specification'
2022-11-25 23:54:43 +05:30
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.
2022-07-16 23:28:13 +05:30
RSpec . describe Glfm :: UpdateSpecification , '#process' do
2022-11-25 23:54:43 +05:30
include NextInstanceOf
2022-07-16 23:28:13 +05:30
subject { described_class . new }
let ( :ghfm_spec_txt_uri ) { described_class :: GHFM_SPEC_TXT_URI }
2022-11-25 23:54:43 +05:30
let ( :ghfm_spec_txt_uri_parsed ) { instance_double ( URI :: HTTPS , :ghfm_spec_txt_uri_parsed ) }
2022-07-16 23:28:13 +05:30
let ( :ghfm_spec_txt_uri_io ) { StringIO . new ( ghfm_spec_txt_contents ) }
2022-11-25 23:54:43 +05:30
let ( :ghfm_spec_md_path ) { described_class :: GHFM_SPEC_MD_PATH }
2022-07-16 23:28:13 +05:30
let ( :ghfm_spec_txt_local_io ) { StringIO . new ( ghfm_spec_txt_contents ) }
2023-01-13 00:05:48 +05:30
let ( :glfm_official_specification_md_path ) { described_class :: GLFM_OFFICIAL_SPECIFICATION_MD_PATH }
let ( :glfm_official_specification_md_io ) { StringIO . new ( glfm_official_specification_md_contents ) }
let ( :glfm_internal_extensions_md_path ) { described_class :: GLFM_INTERNAL_EXTENSIONS_MD_PATH }
let ( :glfm_internal_extensions_md_io ) { StringIO . new ( glfm_internal_extensions_md_contents ) }
2022-07-16 23:28:13 +05:30
let ( :glfm_spec_txt_path ) { described_class :: GLFM_SPEC_TXT_PATH }
let ( :glfm_spec_txt_io ) { StringIO . new }
2022-11-25 23:54:43 +05:30
let ( :glfm_spec_html_path ) { described_class :: GLFM_SPEC_HTML_PATH }
let ( :glfm_spec_html_io ) { StringIO . new }
2023-01-13 00:05:48 +05:30
let ( :es_snapshot_spec_md_path ) { described_class :: ES_SNAPSHOT_SPEC_MD_PATH }
let ( :es_snapshot_spec_md_io ) { StringIO . new }
let ( :es_snapshot_spec_html_path ) { described_class :: ES_SNAPSHOT_SPEC_HTML_PATH }
let ( :es_snapshot_spec_html_io ) { StringIO . new }
2022-11-25 23:54:43 +05:30
let ( :markdown_tempfile_io ) { StringIO . new }
2022-07-16 23:28:13 +05:30
2023-01-13 00:05:48 +05:30
let ( :ghfm_spec_txt_examples ) do
<< ~ MARKDOWN
# Section with Examples
## Emphasis and Strong
` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` example
_EMPHASIS LINE 1 _
_EMPHASIS LINE 2 _
.
< p > < em > EMPHASIS LINE 1 < / em>
< em > EMPHASIS LINE 2 < / em>< / p >
` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `
` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` example
__STRONG! __
.
< p > < strong > STRONG ! < / strong>< / p >
` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` ` `
End of last GitHub examples section .
MARKDOWN
end
2022-07-16 23:28:13 +05:30
let ( :ghfm_spec_txt_contents ) do
2022-10-11 01:57:18 +05:30
<< ~ MARKDOWN
2022-07-16 23:28:13 +05:30
- - -
title : GitHub Flavored Markdown Spec
version : 0 . 29
date : '2019-04-06'
license : '[CC-BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)'
...
# Introduction
2023-01-13 00:05:48 +05:30
GHFM Intro .
2022-07-16 23:28:13 +05:30
2023-01-13 00:05:48 +05:30
#{ghfm_spec_txt_examples}
2022-07-16 23:28:13 +05:30
< ! - - END TESTS - - >
# Appendix
Appendix text .
2022-10-11 01:57:18 +05:30
MARKDOWN
2022-07-16 23:28:13 +05:30
end
2023-01-13 00:05:48 +05:30
let ( :glfm_official_specification_md_examples ) do
2022-10-11 01:57:18 +05:30
<< ~ MARKDOWN
2023-01-13 00:05:48 +05:30
# Official Specification Section with Examples
2022-07-16 23:28:13 +05:30
2023-01-13 00:05:48 +05:30
Some examples .
2022-10-11 01:57:18 +05:30
MARKDOWN
2022-07-16 23:28:13 +05:30
end
2023-01-13 00:05:48 +05:30
let ( :glfm_official_specification_md_contents ) do
2022-10-11 01:57:18 +05:30
<< ~ MARKDOWN
2023-01-13 00:05:48 +05:30
# GLFM Introduction
2022-11-25 23:54:43 +05:30
2023-01-13 00:05:48 +05:30
GLFM intro text .
< ! - - BEGIN TESTS - - >
#{glfm_official_specification_md_examples}
< ! - - END TESTS - - >
# Non-example official content
2022-11-25 23:54:43 +05:30
MARKDOWN
end
2023-01-13 00:05:48 +05:30
let ( :glfm_internal_extensions_md_examples ) do
2022-11-25 23:54:43 +05:30
<< ~ MARKDOWN
# Internal Extension Section with Examples
2022-07-16 23:28:13 +05:30
Some examples .
2022-10-11 01:57:18 +05:30
MARKDOWN
2022-07-16 23:28:13 +05:30
end
2023-01-13 00:05:48 +05:30
let ( :glfm_internal_extensions_md_contents ) do
<< ~ MARKDOWN
# Non-example internal content
< ! - - BEGIN TESTS - - >
#{glfm_internal_extensions_md_examples}
< ! - - END TESTS - - >
# More non-example internal content
MARKDOWN
end
2022-07-16 23:28:13 +05:30
before do
# Mock default ENV var values
2023-01-13 00:05:48 +05:30
stub_env ( 'UPDATE_GHFM_SPEC_MD' )
2022-07-16 23:28:13 +05:30
# 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.
2022-11-25 23:54:43 +05:30
# 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 }
2023-01-13 00:05:48 +05:30
allow ( File ) . to receive ( :open ) . with ( glfm_official_specification_md_path ) do
glfm_official_specification_md_io
2022-11-25 23:54:43 +05:30
end
2023-01-13 00:05:48 +05:30
allow ( File ) . to receive ( :open ) . with ( glfm_internal_extensions_md_path ) do
glfm_internal_extensions_md_io
2022-11-25 23:54:43 +05:30
end
# output files
2022-07-16 23:28:13 +05:30
allow ( File ) . to receive ( :open ) . with ( glfm_spec_txt_path , 'w' ) { glfm_spec_txt_io }
2022-11-25 23:54:43 +05:30
allow ( File ) . to receive ( :open ) . with ( glfm_spec_html_path , 'w' ) { glfm_spec_html_io }
2023-01-13 00:05:48 +05:30
allow ( File ) . to receive ( :open ) . with ( es_snapshot_spec_md_path , 'w' ) { es_snapshot_spec_md_io }
allow ( File ) . to receive ( :open ) . with ( es_snapshot_spec_html_path , 'w' ) { es_snapshot_spec_html_io }
2022-11-25 23:54:43 +05:30
# 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
2022-07-16 23:28:13 +05:30
# Prevent console output when running tests
allow ( subject ) . to receive ( :output )
end
describe 'retrieving latest GHFM spec.txt' do
2022-11-25 23:54:43 +05:30
context 'when UPDATE_GHFM_SPEC_MD is not true (default)' do
2022-07-16 23:28:13 +05:30
it 'does not download' do
2022-11-25 23:54:43 +05:30
expect ( URI ) . not_to receive ( :parse ) . with ( ghfm_spec_txt_uri )
2022-07-16 23:28:13 +05:30
2022-11-25 23:54:43 +05:30
subject . process ( skip_spec_html_generation : true )
2022-07-16 23:28:13 +05:30
expect ( reread_io ( ghfm_spec_txt_local_io ) ) . to eq ( ghfm_spec_txt_contents )
end
end
2022-11-25 23:54:43 +05:30
context 'when UPDATE_GHFM_SPEC_MD is true' do
2022-07-16 23:28:13 +05:30
let ( :ghfm_spec_txt_local_io ) { StringIO . new }
before do
2023-01-13 00:05:48 +05:30
stub_env ( 'UPDATE_GHFM_SPEC_MD' , 'true' )
2022-11-25 23:54:43 +05:30
allow ( File ) . to receive ( :open ) . with ( ghfm_spec_md_path , 'w' ) { ghfm_spec_txt_local_io }
2022-07-16 23:28:13 +05:30
end
context 'with success' do
it 'downloads and saves' do
2022-11-25 23:54:43 +05:30
subject . process ( skip_spec_html_generation : true )
2022-07-16 23:28:13 +05:30
expect ( reread_io ( ghfm_spec_txt_local_io ) ) . to eq ( ghfm_spec_txt_contents )
end
end
context 'with error handling' do
context 'with a version mismatch' do
let ( :ghfm_spec_txt_contents ) do
2022-10-11 01:57:18 +05:30
<< ~ MARKDOWN
2022-07-16 23:28:13 +05:30
- - -
title : GitHub Flavored Markdown Spec
version : 0 . 30
...
2022-10-11 01:57:18 +05:30
MARKDOWN
2022-07-16 23:28:13 +05:30
end
it 'raises an error' do
2022-11-25 23:54:43 +05:30
expect do
subject . process ( skip_spec_html_generation : true )
end . to raise_error / version mismatch.*expected.*29.*got.*30 /i
2022-07-16 23:28:13 +05:30
end
end
context 'with a failed read of file lines' do
let ( :ghfm_spec_txt_contents ) { '' }
it 'raises an error if lines cannot be read' do
2022-11-25 23:54:43 +05:30
expect { subject . process ( skip_spec_html_generation : true ) } . to raise_error / unable to read lines /i
2022-07-16 23:28:13 +05:30
end
end
context 'with a failed re-read of file string' do
before do
allow ( ghfm_spec_txt_uri_io ) . to receive ( :read ) . and_return ( nil )
end
it 'raises an error if file is blank' do
2022-11-25 23:54:43 +05:30
expect { subject . process ( skip_spec_html_generation : true ) } . to raise_error / unable to read string /i
2022-07-16 23:28:13 +05:30
end
end
end
end
end
2023-01-13 00:05:48 +05:30
describe 'writing output_spec/spec.txt' do
let ( :glfm_spec_txt_contents ) { reread_io ( glfm_spec_txt_io ) }
2022-07-16 23:28:13 +05:30
before do
2022-11-25 23:54:43 +05:30
subject . process ( skip_spec_html_generation : true )
2022-07-16 23:28:13 +05:30
end
2023-01-13 00:05:48 +05:30
it 'includes only the header and official examples' do
expected = described_class :: GLFM_SPEC_TXT_HEADER + glfm_official_specification_md_contents
expect ( glfm_spec_txt_contents ) . to eq ( expected )
2022-07-16 23:28:13 +05:30
end
2023-01-13 00:05:48 +05:30
end
2022-07-16 23:28:13 +05:30
2023-01-13 00:05:48 +05:30
describe 'writing output_example_snapshots/snapshot_spec.md' do
let ( :es_snapshot_spec_md_contents ) { reread_io ( es_snapshot_spec_md_io ) }
2022-11-25 23:54:43 +05:30
2023-01-13 00:05:48 +05:30
before do
subject . process ( skip_spec_html_generation : true )
end
2022-07-16 23:28:13 +05:30
2023-01-13 00:05:48 +05:30
it 'replaces the header text with the GitLab version' do
expect ( es_snapshot_spec_md_contents ) . not_to match ( / GitHub Flavored Markdown Spec /m )
expect ( es_snapshot_spec_md_contents ) . not_to match ( / ^version: \ d \ . \ d /m )
expect ( es_snapshot_spec_md_contents ) . not_to match ( / ^date: /m )
2022-07-16 23:28:13 +05:30
2023-01-13 00:05:48 +05:30
expect ( es_snapshot_spec_md_contents ) . to match ( / #{ Regexp . escape ( described_class :: GLFM_SPEC_TXT_HEADER ) } /mo )
end
2022-07-16 23:28:13 +05:30
2023-01-13 00:05:48 +05:30
it 'includes header and all examples' , :unlimited_max_formatted_output_length do
# rubocop:disable Style/StringConcatenation (string contatenation is more readable)
expected = described_class :: GLFM_SPEC_TXT_HEADER +
ghfm_spec_txt_examples +
" \n " +
glfm_official_specification_md_examples +
" \n \n " + # NOTE: We want a blank line between the official and internal examples
glfm_internal_extensions_md_examples +
" \n "
# rubocop:enable Style/StringConcatenation
expect ( es_snapshot_spec_md_contents ) . to eq ( expected )
2022-07-16 23:28:13 +05:30
end
end
2023-01-13 00:05:48 +05:30
# rubocop:disable RSpec/MultipleMemoizedHelpers
describe 'writing output html files' do
let ( :spec_html_contents ) { reread_io ( glfm_spec_html_io ) }
let ( :snapshot_spec_html_contents ) { reread_io ( es_snapshot_spec_html_io ) }
2022-11-25 23:54:43 +05:30
before do
subject . process
end
2023-01-13 00:05:48 +05:30
it 'renders expected HTML' , :unlimited_max_formatted_output_length do
# NOTE: We do assertions for both output HTML files in this same `it` example block,
# because calling a full `subject.process` without `skip_spec_html_generation: true`
# is very slow, and want to avoid doing it twice.
expected_spec_html = << ~ RENDERED_HTML
< div class = " gl-relative markdown-code-block js-markdown-code " >
< pre data - sourcepos = " 1:1-4:3 " lang = " yaml " class = " code highlight js-syntax-highlight language-yaml " data - lang - params = " frontmatter " v - pre = " true " > < code > < span id = " LC1 " class = " line " lang = " yaml " > < span class = " na " > title < / span><span class="pi">:< /s pan > < span class = " s " > GitLab Flavored Markdown ( GLFM ) Spec < / span>< /s pan >
< span id = " LC2 " class = " line " lang = " yaml " > < span class = " na " > version < / span><span class="pi">:< /s pan > < span class = " s " > alpha < / span>< /s pan > < / code>< / pre >
< copy - code > < / copy-code>
< / div>
< h1 data - sourcepos = " 5:1-5:19 " dir = " auto " >
< a id = " user-content-glfm-introduction " class = " anchor " href = " # glfm-introduction " aria - hidden = " true " > < / a>GLFM Introduction< / h1 >
< p data - sourcepos = " 7:1-7:16 " dir = " auto " > GLFM intro text . < / p>
< h1 data - sourcepos = " 10:1-10: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 = " 12:1-12:14 " dir = " auto " > Some examples . < / p>
< h1 data - sourcepos = " 15:1-15:30 " dir = " auto " >
< a id = " user-content-non-example-official-content " class = " anchor " href = " # non-example-official-content " aria - hidden = " true " > < / a>Non-example official content< / h1 >
RENDERED_HTML
expect ( spec_html_contents ) . to be == expected_spec_html
expected_snapshot_spec_html = << ~ RENDERED_HTML
2022-11-25 23:54:43 +05:30
< div class = " gl-relative markdown-code-block js-markdown-code " >
2023-01-13 00:05:48 +05:30
< pre data - sourcepos = " 1:1-4:3 " lang = " yaml " class = " code highlight js-syntax-highlight language-yaml " data - lang - params = " frontmatter " v - pre = " true " > < code > < span id = " LC1 " class = " line " lang = " yaml " > < span class = " na " > title < / span><span class="pi">:< /s pan > < span class = " s " > GitLab Flavored Markdown ( GLFM ) Spec < / span>< /s pan >
2022-11-25 23:54:43 +05:30
< span id = " LC2 " class = " line " lang = " yaml " > < span class = " na " > version < / span><span class="pi">:< /s pan > < span class = " s " > alpha < / span>< /s pan > < / code>< / pre >
< copy - code > < / copy-code>
< / div>
2023-01-13 00:05:48 +05:30
< h1 data - sourcepos = " 5:1-5:23 " dir = " auto " >
2022-11-25 23:54:43 +05:30
< a id = " user-content-section-with-examples " class = " anchor " href = " # section-with-examples " aria - hidden = " true " > < / a>Section with Examples< / h1 >
2023-01-13 00:05:48 +05:30
< h2 data - sourcepos = " 7:1-7:22 " dir = " auto " >
< a id = " user-content-emphasis-and-strong " class = " anchor " href = " # emphasis-and-strong " aria - hidden = " true " > < / a>Emphasis and Strong< / h2 >
2022-11-25 23:54:43 +05:30
< div class = " gl-relative markdown-code-block js-markdown-code " >
2023-01-13 00:05:48 +05:30
< pre data - sourcepos = " 9:1-12:32 " lang = " plaintext " class = " code highlight js-syntax-highlight language-plaintext " data - canonical - lang = " example " v - pre = " true " > < code > < span id = " LC1 " class = " line " lang = " plaintext " > _EMPHASIS LINE 1 _ < / span>
< span id = " LC2 " class = " line " lang = " plaintext " > _EMPHASIS LINE 2 _ < / span>< / code > < / pre>
2022-11-25 23:54:43 +05:30
< copy - code > < / copy-code>
< / div>
2023-01-13 00:05:48 +05:30
< div class = " gl-relative markdown-code-block js-markdown-code " >
< pre data - sourcepos = " 14:1-17:32 " lang = " plaintext " class = " code highlight js-syntax-highlight language-plaintext " data - canonical - lang = " " v - pre = " true " > < code > < span id = " LC1 " class = " line " lang = " plaintext " > & lt ; p & gt ; & lt ; em & gt ; EMPHASIS LINE 1 & lt ; / em & gt ; < / span>
< span id = " LC2 " class = " line " lang = " plaintext " > & lt ; em & gt ; EMPHASIS LINE 2 & lt ; / em & gt ; & lt ; / p & gt ; < / span>< / code > < / pre>
< copy - code > < / copy-code>
< / div>
< div class = " gl-relative markdown-code-block js-markdown-code " >
< pre data - sourcepos = " 19:1-21:32 " lang = " plaintext " class = " code highlight js-syntax-highlight language-plaintext " data - canonical - lang = " example " v - pre = " true " > < code > < span id = " LC1 " class = " line " lang = " plaintext " > __STRONG! __ < / span>< / code > < / pre>
< copy - code > < / copy-code>
< / div>
< div class = " gl-relative markdown-code-block js-markdown-code " >
< pre data - sourcepos = " 23:1-25:32 " lang = " plaintext " class = " code highlight js-syntax-highlight language-plaintext " data - canonical - lang = " " v - pre = " true " > < code > < span id = " LC1 " class = " line " lang = " plaintext " > & lt ; p & gt ; & lt ; strong & gt ; STRONG ! & lt ; / strong & gt ; & lt ; / p & gt ; < / span>< / code > < / pre>
< copy - code > < / copy-code>
< / div>
< p data - sourcepos = " 27:1-27:36 " dir = " auto " > End of last GitHub examples section . < / p>
< h1 data - sourcepos = " 29:1-29:46 " dir = " auto " >
2022-11-25 23:54:43 +05:30
< 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 >
2023-01-13 00:05:48 +05:30
< p data - sourcepos = " 31:1-31:14 " dir = " auto " > Some examples . < / p>
< h1 data - sourcepos = " 34:1-34:42 " dir = " auto " >
2022-11-25 23:54:43 +05:30
< 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 >
2023-01-13 00:05:48 +05:30
< p data - sourcepos = " 36:1-36:14 " dir = " auto " > Some examples . < / p>
RENDERED_HTML
expect ( snapshot_spec_html_contents ) . to be == expected_snapshot_spec_html
2022-11-25 23:54:43 +05:30
end
end
2023-01-13 00:05:48 +05:30
# rubocop:enable RSpec/MultipleMemoizedHelpers
2022-11-25 23:54:43 +05:30
2022-07-16 23:28:13 +05:30
def reread_io ( io )
# Reset the io StringIO to the beginning position of the buffer
io . seek ( 0 )
io . read
end
end