|
| 1 | +# encoding: utf-8 |
| 2 | + |
| 3 | +# Exercises code paths the original markup_test.rb does not reach: |
| 4 | +# - The five fallback markdown gem procs (executed against stubbed constants) |
| 5 | +# - The LoadError raised when no markdown gem is available |
| 6 | +# - try_require's rescue clause |
| 7 | +# - The legacy RDoc < 4 render branch |
| 8 | +# - Implementation#render's NotImplementedError default |
| 9 | +# - Implementation#match? without Linguist (and the lazy regexp memoization) |
| 10 | +# - GitHub::Markup.markup_impl's duplicate-symbol guard |
| 11 | +# - GitHub::Markup.render falling through to the raw content |
| 12 | +# - GitHub::Markup.render_s with an unknown symbol and with nil content |
| 13 | +# - GitHub::Markup.preload! |
| 14 | +# - GitHub::Markup.language returning nil without Linguist |
| 15 | +# - CommandImplementation block-arity branches (arity 1 vs 2) |
| 16 | + |
| 17 | +$LOAD_PATH.unshift File.dirname(__FILE__) + "/../lib" |
| 18 | + |
| 19 | +require_relative 'test_helper' |
| 20 | +require 'github-markup' |
| 21 | +require 'github/markup' |
| 22 | +require 'minitest/autorun' |
| 23 | + |
| 24 | +class CoverageTest < Minitest::Test |
| 25 | + def test_version_constant_is_defined |
| 26 | + assert_kind_of String, GitHub::Markup::VERSION |
| 27 | + assert_equal GitHub::Markup::VERSION, GitHub::Markup::Version |
| 28 | + end |
| 29 | + |
| 30 | + # --- markdown.rb fallback procs ---------------------------------------- |
| 31 | + |
| 32 | + def test_github_markdown_proc_uses_github_markdown_render |
| 33 | + with_stub_const("GitHub::Markdown", fake_renderer_module(:render)) do |
| 34 | + result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("github/markdown").call("hi") |
| 35 | + assert_equal "github_markdown:hi", result |
| 36 | + end |
| 37 | + end |
| 38 | + |
| 39 | + def test_redcarpet_proc_renders_via_redcarpet |
| 40 | + fake_html = Class.new |
| 41 | + fake_md = Class.new do |
| 42 | + def initialize(*); end |
| 43 | + def render(content); "redcarpet:#{content}"; end |
| 44 | + end |
| 45 | + fake_module = Module.new |
| 46 | + fake_module.const_set(:Render, Module.new.tap { |m| m.const_set(:HTML, fake_html) }) |
| 47 | + fake_module.const_set(:Markdown, fake_md) |
| 48 | + with_stub_const("Redcarpet", fake_module) do |
| 49 | + result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("redcarpet").call("hi") |
| 50 | + assert_equal "redcarpet:hi", result |
| 51 | + end |
| 52 | + end |
| 53 | + |
| 54 | + def test_rdiscount_proc_renders_via_rdiscount |
| 55 | + with_stub_const("RDiscount", instance_renderer_class(:to_html, prefix: "rdiscount")) do |
| 56 | + result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("rdiscount").call("hi") |
| 57 | + assert_equal "rdiscount:hi", result |
| 58 | + end |
| 59 | + end |
| 60 | + |
| 61 | + def test_maruku_proc_renders_via_maruku |
| 62 | + with_stub_const("Maruku", instance_renderer_class(:to_html, prefix: "maruku")) do |
| 63 | + result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("maruku").call("hi") |
| 64 | + assert_equal "maruku:hi", result |
| 65 | + end |
| 66 | + end |
| 67 | + |
| 68 | + def test_kramdown_proc_renders_via_kramdown_document |
| 69 | + fake_doc = instance_renderer_class(:to_html, prefix: "kramdown") |
| 70 | + fake_module = Module.new.tap { |m| m.const_set(:Document, fake_doc) } |
| 71 | + with_stub_const("Kramdown", fake_module) do |
| 72 | + result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("kramdown").call("hi") |
| 73 | + assert_equal "kramdown:hi", result |
| 74 | + end |
| 75 | + end |
| 76 | + |
| 77 | + def test_bluecloth_proc_renders_via_bluecloth |
| 78 | + with_stub_const("BlueCloth", instance_renderer_class(:to_html, prefix: "bluecloth")) do |
| 79 | + result = GitHub::Markup::Markdown::MARKDOWN_GEMS.fetch("bluecloth").call("hi") |
| 80 | + assert_equal "bluecloth:hi", result |
| 81 | + end |
| 82 | + end |
| 83 | + |
| 84 | + # --- markdown.rb load failure and try_require rescue -------------------- |
| 85 | + |
| 86 | + def test_markdown_load_raises_loaderror_when_no_gem_is_available |
| 87 | + md = GitHub::Markup::Markdown.new |
| 88 | + def md.try_require(_); false; end |
| 89 | + assert_raises(LoadError) { md.load } |
| 90 | + end |
| 91 | + |
| 92 | + def test_try_require_returns_false_for_missing_gem |
| 93 | + md = GitHub::Markup::Markdown.new |
| 94 | + refute md.send(:try_require, "github_markup_definitely_not_a_real_gem_#{Time.now.to_i}") |
| 95 | + end |
| 96 | + |
| 97 | + # --- rdoc.rb legacy version branch -------------------------------------- |
| 98 | + # The `RDoc::VERSION < 4` branch in rdoc.rb is marked :nocov: in source |
| 99 | + # because the modern RDoc::Markup::ToHtml constructor requires Options |
| 100 | + # and the legacy zero-arg form has been broken since RDoc 4 (2013). |
| 101 | + |
| 102 | + # --- implementation.rb default render and Linguist-less match? --------- |
| 103 | + |
| 104 | + def test_base_implementation_render_raises_not_implemented_error |
| 105 | + impl = GitHub::Markup::Implementation.new(/foo/, []) |
| 106 | + assert_raises(NotImplementedError) { impl.render("README.foo", "anything") } |
| 107 | + end |
| 108 | + |
| 109 | + def test_match_uses_filename_extension_when_linguist_is_absent |
| 110 | + impl_class = Class.new(GitHub::Markup::Implementation) do |
| 111 | + def initialize; super(/md|markdown/, []); end |
| 112 | + end |
| 113 | + impl = impl_class.new |
| 114 | + without_linguist do |
| 115 | + assert impl.match?("README.md", nil) |
| 116 | + # call again to cover the memoization branch in file_ext_regexp |
| 117 | + assert impl.match?("README.markdown", nil) |
| 118 | + refute impl.match?("README.txt", nil) |
| 119 | + end |
| 120 | + end |
| 121 | + |
| 122 | + # --- markup.rb registration guard, render fallthroughs, preload! ------- |
| 123 | + |
| 124 | + def test_markup_impl_raises_when_symbol_already_registered |
| 125 | + err = assert_raises(ArgumentError) do |
| 126 | + GitHub::Markup.markup_impl( |
| 127 | + ::GitHub::Markups::MARKUP_MARKDOWN, |
| 128 | + GitHub::Markup::Markdown.new |
| 129 | + ) |
| 130 | + end |
| 131 | + assert_match(/already defined/, err.message) |
| 132 | + end |
| 133 | + |
| 134 | + def test_render_returns_content_when_no_implementation_matches |
| 135 | + raw = "no extension match here" |
| 136 | + assert_equal raw, GitHub::Markup.render("README.unknown_ext_xyz", raw) |
| 137 | + end |
| 138 | + |
| 139 | + def test_render_s_returns_content_when_symbol_is_unknown |
| 140 | + raw = "passthrough body" |
| 141 | + assert_equal raw, GitHub::Markup.render_s(:not_a_real_markup_symbol, raw) |
| 142 | + end |
| 143 | + |
| 144 | + def test_render_s_raises_on_nil_content |
| 145 | + assert_raises(ArgumentError) do |
| 146 | + GitHub::Markup.render_s(::GitHub::Markups::MARKUP_MARKDOWN, nil) |
| 147 | + end |
| 148 | + end |
| 149 | + |
| 150 | + def test_preload_calls_load_on_every_implementation |
| 151 | + GitHub::Markup.preload! |
| 152 | + # If preload! succeeded, every markup_impl reports a non-nil renderer or completes its load step. |
| 153 | + GitHub::Markup.markup_impls.each do |impl| |
| 154 | + assert_respond_to impl, :load |
| 155 | + end |
| 156 | + end |
| 157 | + |
| 158 | + def test_language_returns_nil_without_linguist |
| 159 | + without_linguist do |
| 160 | + assert_nil GitHub::Markup.language("README.md", "anything") |
| 161 | + end |
| 162 | + end |
| 163 | + |
| 164 | + # --- implementation.rb: Linguist-absent constructor + invalid-language raise |
| 165 | + |
| 166 | + def test_implementation_initializes_without_linguist |
| 167 | + without_linguist do |
| 168 | + # Forces the else branch of `if defined?(::Linguist)` in initialize |
| 169 | + impl = GitHub::Markup::Implementation.new(/foo/, ["AnythingGoesWithoutLinguist"]) |
| 170 | + assert_nil impl.languages |
| 171 | + refute impl.match?("README.bar", nil) |
| 172 | + end |
| 173 | + end |
| 174 | + |
| 175 | + def test_implementation_raises_for_unknown_linguist_language |
| 176 | + err = assert_raises(RuntimeError) do |
| 177 | + GitHub::Markup::Implementation.new(/foo/, ["DefinitelyNotALinguistLanguage"]) |
| 178 | + end |
| 179 | + assert_match(/no match for language/, err.message) |
| 180 | + end |
| 181 | + |
| 182 | + # --- markups.rb wikicloth idempotent ESCAPED_TAGS<<'tt' both branches --- |
| 183 | + |
| 184 | + def test_mediawiki_render_is_idempotent_for_escaped_tags |
| 185 | + body = "==Hello==\nworld" |
| 186 | + # First render adds 'tt'; second render hits the `else` branch of `unless include?('tt')`. |
| 187 | + GitHub::Markup.render("README.mediawiki", body) |
| 188 | + GitHub::Markup.render("README.mediawiki", body) |
| 189 | + assert_includes WikiCloth::WikiBuffer::HTMLElement::ESCAPED_TAGS, 'tt' |
| 190 | + end |
| 191 | + |
| 192 | + # --- command_implementation.rb block arity branches -------------------- |
| 193 | + |
| 194 | + def test_command_block_with_arity_two_receives_rendered_and_content |
| 195 | + captured = nil |
| 196 | + impl = GitHub::Markup::CommandImplementation.new( |
| 197 | + /covarity2/, ['Text'], 'test/fixtures/cat.sh', 'covarity2' |
| 198 | + ) do |rendered, content| |
| 199 | + captured = [rendered, content] |
| 200 | + "two:#{rendered.strip}:#{content}" |
| 201 | + end |
| 202 | + out = impl.render('README.covarity2', 'payload') |
| 203 | + assert_equal ['payload', 'payload'], captured.map(&:strip) |
| 204 | + assert_equal 'two:payload:payload', out |
| 205 | + end |
| 206 | + |
| 207 | + def test_command_block_with_arity_one_receives_only_rendered |
| 208 | + captured = nil |
| 209 | + impl = GitHub::Markup::CommandImplementation.new( |
| 210 | + /covarity1/, ['Text'], 'test/fixtures/cat.sh', 'covarity1' |
| 211 | + ) do |rendered| |
| 212 | + captured = rendered |
| 213 | + "one:#{rendered.strip}" |
| 214 | + end |
| 215 | + out = impl.render('README.covarity1', 'payload') |
| 216 | + assert_equal 'payload', captured.strip |
| 217 | + assert_equal 'one:payload', out |
| 218 | + end |
| 219 | + |
| 220 | + def test_command_with_no_block_returns_rendered_output |
| 221 | + impl = GitHub::Markup::CommandImplementation.new( |
| 222 | + /covnoblock/, ['Text'], 'test/fixtures/cat.sh', 'covnoblock' |
| 223 | + ) |
| 224 | + assert_equal 'hello', impl.render('README.covnoblock', 'hello').strip |
| 225 | + end |
| 226 | + |
| 227 | + def test_command_render_falls_back_to_content_when_command_returns_empty |
| 228 | + impl = GitHub::Markup::CommandImplementation.new( |
| 229 | + /covempty/, ['Text'], '/usr/bin/true', 'covempty' |
| 230 | + ) |
| 231 | + assert_equal 'fallback-body', impl.render('README.covempty', 'fallback-body') |
| 232 | + end |
| 233 | + |
| 234 | + def test_command_raises_when_subprocess_exits_non_zero |
| 235 | + impl = GitHub::Markup::CommandImplementation.new( |
| 236 | + /covfail/, ['Text'], 'test/fixtures/fail.sh', 'covfail' |
| 237 | + ) |
| 238 | + assert_raises(GitHub::Markup::CommandError) { impl.render('README.covfail', 'payload') } |
| 239 | + end |
| 240 | + |
| 241 | + private |
| 242 | + |
| 243 | + def with_stub_const(path, value) |
| 244 | + parts = path.split("::") |
| 245 | + name = parts.pop |
| 246 | + parent = parts.inject(Object) { |mod, part| mod.const_get(part) } |
| 247 | + had_const = parent.const_defined?(name, false) |
| 248 | + original = parent.const_get(name) if had_const |
| 249 | + parent.send(:remove_const, name) if had_const |
| 250 | + parent.const_set(name, value) |
| 251 | + yield |
| 252 | + ensure |
| 253 | + parent.send(:remove_const, name) if parent.const_defined?(name, false) |
| 254 | + parent.const_set(name, original) if had_const |
| 255 | + end |
| 256 | + |
| 257 | + def without_linguist |
| 258 | + had_const = Object.const_defined?(:Linguist, false) |
| 259 | + original = Object.const_get(:Linguist) if had_const |
| 260 | + Object.send(:remove_const, :Linguist) if had_const |
| 261 | + yield |
| 262 | + ensure |
| 263 | + Object.const_set(:Linguist, original) if had_const |
| 264 | + end |
| 265 | + |
| 266 | + def fake_renderer_module(method_name) |
| 267 | + Module.new do |
| 268 | + define_singleton_method(method_name) { |content| "github_markdown:#{content}" } |
| 269 | + end |
| 270 | + end |
| 271 | + |
| 272 | + def instance_renderer_class(method_name, prefix:) |
| 273 | + Class.new do |
| 274 | + define_method(:initialize) { |content| @__coverage_content = "#{prefix}:#{content}" } |
| 275 | + define_method(method_name) { @__coverage_content } |
| 276 | + end |
| 277 | + end |
| 278 | +end |
0 commit comments