bitclust-core 1.3.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/README.md +65 -0
  4. data/Rakefile +32 -0
  5. data/data/bitclust/catalog/ja_JP.UTF-8 +19 -1
  6. data/data/bitclust/searchpage/index.html +90 -0
  7. data/data/bitclust/template/class +69 -8
  8. data/data/bitclust/template/function +2 -2
  9. data/data/bitclust/template/layout +11 -3
  10. data/data/bitclust/template/method +2 -2
  11. data/data/bitclust/template/search +1 -1
  12. data/data/bitclust/template.epub/class +22 -1
  13. data/data/bitclust/template.epub/function +2 -2
  14. data/data/bitclust/template.epub/layout +1 -3
  15. data/data/bitclust/template.epub/method +2 -2
  16. data/data/bitclust/template.lillia/class +26 -5
  17. data/data/bitclust/template.lillia/layout +1 -3
  18. data/data/bitclust/template.lillia/method +2 -2
  19. data/data/bitclust/template.offline/class +48 -4
  20. data/data/bitclust/template.offline/function +2 -2
  21. data/data/bitclust/template.offline/layout +27 -3
  22. data/data/bitclust/template.offline/method +2 -2
  23. data/lib/bitclust/app.rb +16 -17
  24. data/lib/bitclust/capi_converter.rb +20 -0
  25. data/lib/bitclust/classentry.rb +67 -17
  26. data/lib/bitclust/compat.rb +2 -2
  27. data/lib/bitclust/completion.rb +58 -27
  28. data/lib/bitclust/crossrubyutils.rb +8 -5
  29. data/lib/bitclust/database.rb +28 -8
  30. data/lib/bitclust/doc_converter.rb +63 -0
  31. data/lib/bitclust/docentry.rb +1 -1
  32. data/lib/bitclust/entity_splitter.rb +221 -0
  33. data/lib/bitclust/entry.rb +47 -7
  34. data/lib/bitclust/functiondatabase.rb +13 -1
  35. data/lib/bitclust/functionentry.rb +5 -1
  36. data/lib/bitclust/functionreferenceparser.rb +4 -4
  37. data/lib/bitclust/generators/epub.rb +10 -3
  38. data/lib/bitclust/htmlutils.rb +1 -1
  39. data/lib/bitclust/include_graph.rb +375 -0
  40. data/lib/bitclust/include_pruner.rb +144 -0
  41. data/lib/bitclust/interface.rb +1 -1
  42. data/lib/bitclust/libraryentry.rb +8 -7
  43. data/lib/bitclust/lineinput.rb +7 -5
  44. data/lib/bitclust/link_checker.rb +179 -0
  45. data/lib/bitclust/markdown_bridge.rb +186 -0
  46. data/lib/bitclust/markdown_orchestrator.rb +267 -0
  47. data/lib/bitclust/markdown_to_rrd.rb +824 -0
  48. data/lib/bitclust/markdown_tree.rb +217 -0
  49. data/lib/bitclust/mdcompiler.rb +883 -0
  50. data/lib/bitclust/mdparser.rb +347 -0
  51. data/lib/bitclust/messagecatalog.rb +3 -3
  52. data/lib/bitclust/method_since_calculator.rb +117 -0
  53. data/lib/bitclust/methoddatabase.rb +128 -14
  54. data/lib/bitclust/methodentry.rb +125 -7
  55. data/lib/bitclust/methodid.rb +12 -11
  56. data/lib/bitclust/methodsignature.rb +10 -4
  57. data/lib/bitclust/nameutils.rb +41 -22
  58. data/lib/bitclust/preprocessor.rb +66 -29
  59. data/lib/bitclust/rdcompiler.rb +195 -95
  60. data/lib/bitclust/refsdatabase.rb +23 -8
  61. data/lib/bitclust/reloadable_request_handler.rb +72 -0
  62. data/lib/bitclust/requesthandler.rb +21 -16
  63. data/lib/bitclust/ridatabase.rb +10 -4
  64. data/lib/bitclust/rrd_to_markdown.rb +925 -0
  65. data/lib/bitclust/rrdparser.rb +196 -30
  66. data/lib/bitclust/runner.rb +17 -2
  67. data/lib/bitclust/screen.rb +80 -11
  68. data/lib/bitclust/search_index_generator.rb +288 -0
  69. data/lib/bitclust/searcher.rb +69 -20
  70. data/lib/bitclust/simplesearcher.rb +24 -12
  71. data/lib/bitclust/subcommand.rb +7 -1
  72. data/lib/bitclust/subcommands/ancestors_command.rb +9 -2
  73. data/lib/bitclust/subcommands/checklink_command.rb +54 -0
  74. data/lib/bitclust/subcommands/chm_command.rb +15 -7
  75. data/lib/bitclust/subcommands/classes_command.rb +5 -0
  76. data/lib/bitclust/subcommands/extract_command.rb +5 -0
  77. data/lib/bitclust/subcommands/htmlfile_command.rb +21 -7
  78. data/lib/bitclust/subcommands/init_command.rb +4 -1
  79. data/lib/bitclust/subcommands/list_command.rb +12 -4
  80. data/lib/bitclust/subcommands/lookup_command.rb +28 -9
  81. data/lib/bitclust/subcommands/methods_command.rb +10 -2
  82. data/lib/bitclust/subcommands/methodsince_command.rb +80 -0
  83. data/lib/bitclust/subcommands/preproc_command.rb +5 -0
  84. data/lib/bitclust/subcommands/property_command.rb +2 -1
  85. data/lib/bitclust/subcommands/searchpage_command.rb +102 -0
  86. data/lib/bitclust/subcommands/server_command.rb +10 -8
  87. data/lib/bitclust/subcommands/setup_command.rb +24 -10
  88. data/lib/bitclust/subcommands/statichtml_command.rb +129 -6
  89. data/lib/bitclust/subcommands/update_command.rb +110 -2
  90. data/lib/bitclust/syntax_highlighter.rb +48 -4
  91. data/lib/bitclust/textutils.rb +3 -3
  92. data/lib/bitclust/version.rb +1 -1
  93. data/lib/bitclust/version_badges.rb +90 -0
  94. data/lib/bitclust/whole_file_gate.rb +137 -0
  95. data/test/test_app.rb +269 -0
  96. data/test/test_bitclust.rb +2 -0
  97. data/test/test_capi_converter.rb +91 -0
  98. data/test/test_class_screen.rb +413 -0
  99. data/test/test_copy_doc_md.rb +53 -0
  100. data/test/test_doc_converter.rb +78 -0
  101. data/test/test_entity_splitter.rb +231 -0
  102. data/test/test_entry.rb +207 -2
  103. data/test/test_eol_warning.rb +43 -0
  104. data/test/test_functionreferenceparser.rb +11 -0
  105. data/test/test_include_graph.rb +640 -0
  106. data/test/test_include_pruner.rb +133 -0
  107. data/test/test_link_checker.rb +114 -0
  108. data/test/test_lookup_command.rb +135 -0
  109. data/test/test_markdown_bridge.rb +217 -0
  110. data/test/test_markdown_orchestrator.rb +478 -0
  111. data/test/test_markdown_to_rrd.rb +757 -0
  112. data/test/test_markdown_tree.rb +369 -0
  113. data/test/test_mdcompiler.rb +1276 -0
  114. data/test/test_mdparser.rb +573 -0
  115. data/test/test_method_screen.rb +166 -0
  116. data/test/test_method_since_calculator.rb +290 -0
  117. data/test/test_methoddatabase.rb +16 -0
  118. data/test/test_methodentry.rb +328 -0
  119. data/test/test_methodsignature.rb +3 -1
  120. data/test/test_methodsince_command.rb +168 -0
  121. data/test/test_nameutils.rb +22 -0
  122. data/test/test_preprocessor.rb +215 -0
  123. data/test/test_rdcompiler.rb +401 -30
  124. data/test/test_refsdatabase.rb +32 -0
  125. data/test/test_rrd_to_markdown.rb +968 -0
  126. data/test/test_rrdparser.rb +295 -2
  127. data/test/test_run_ruby_wasm.rb +54 -0
  128. data/test/test_run_worker_prelude.rb +83 -0
  129. data/test/test_runner.rb +51 -0
  130. data/test/test_search_index_generator.rb +513 -0
  131. data/test/test_search_screen.rb +48 -0
  132. data/test/test_searcher.rb +132 -0
  133. data/test/test_searchpage_command.rb +167 -0
  134. data/test/test_simplesearcher.rb +28 -0
  135. data/test/test_statichtml_command.rb +204 -0
  136. data/test/test_syntax_highlighter.rb +60 -0
  137. data/test/test_whole_file_gate.rb +175 -0
  138. data/theme/default/js/NOTICE +42 -0
  139. data/theme/default/js/run-worker.js +111 -0
  140. data/theme/default/js/run.js +350 -0
  141. data/theme/default/js/search_controller.js +134 -0
  142. data/theme/default/js/search_init.js +176 -0
  143. data/theme/default/js/search_navigation.js +110 -0
  144. data/theme/default/js/search_page.js +202 -0
  145. data/theme/default/js/search_ranker.js +244 -0
  146. data/theme/default/script.js +91 -18
  147. data/theme/default/search.css +149 -0
  148. data/theme/default/style.css +193 -21
  149. data/theme/default/syntax-highlight.css +8 -0
  150. data/theme/lillia/style.css +45 -1
  151. metadata +161 -20
  152. data/README +0 -32
@@ -0,0 +1,166 @@
1
+ require 'test/unit'
2
+ require 'bitclust'
3
+ require 'bitclust/screen'
4
+
5
+ # Covers data/bitclust/template.offline, used by `bitclust statichtml` (the
6
+ # production docs.ruby-lang.org build) and `bitclust chm`.
7
+ #
8
+ # NOTE: only one templatedir is exercised here on purpose. Screen#run_template
9
+ # caches the compiled ERB template methods on the *class* (MethodScreen),
10
+ # keyed only by method name (e.g. "method_template"), regardless of which
11
+ # @template_repository the instance was built with. So constructing
12
+ # MethodScreen from two different templatedirs (e.g. this one and
13
+ # data/bitclust/template) within the same process is unreliable: whichever
14
+ # templatedir's ERB gets compiled first "wins" for every MethodScreen built
15
+ # afterwards, in every test file, for the rest of the run. This is a
16
+ # pre-existing quirk of Screen, not something introduced here.
17
+ #
18
+ # data/bitclust/template, data/bitclust/template.lillia and
19
+ # data/bitclust/template.epub were hand-verified (each in its own process,
20
+ # not committed as an automated test here) to build @title the same way as
21
+ # template.offline below, just keeping their pre-existing
22
+ # "#{entry.type_label} #{entry.label}" prefix -- i.e. the fix is
23
+ # entry.label -> entry.title_labels.join(', ') in all four `method`
24
+ # templates, so the CHM (template) and EPUB (template.epub) screens get the
25
+ # same alias listing.
26
+ class TestMethodScreenTitle < Test::Unit::TestCase
27
+ SRC = <<'HERE'
28
+ = class Enumerable2 < Object
29
+ enumerable class
30
+ == Instance Methods
31
+ --- collect -> Array
32
+ --- map -> Array
33
+
34
+ 説明のためのエントリ
35
+
36
+ --- select -> Array
37
+
38
+ 説明2
39
+ HERE
40
+
41
+ def setup
42
+ @lib, @db = BitClust::RRDParser.parse(SRC, 'testlib')
43
+ datadir = File.expand_path('../data/bitclust', __dir__)
44
+ @manager = BitClust::ScreenManager.new(
45
+ :templatedir => "#{datadir}/template.offline",
46
+ :catalogdir => "#{datadir}/catalog",
47
+ :encoding => 'utf-8',
48
+ :default_encoding => 'utf-8',
49
+ :base_url => '',
50
+ :target_version => '3.4'
51
+ )
52
+ end
53
+
54
+ def method_html(spec)
55
+ entry = @db.get_method(BitClust::MethodSpec.parse(spec))
56
+ @manager.method_screen([entry], :database => @db).body
57
+ end
58
+
59
+ def title_of(html)
60
+ html[/<title>(.*?) \(Ruby/, 1]
61
+ end
62
+
63
+ # template.offline's <title> has never included the "instance method"
64
+ # type_label prefix (only entry.label); the alias fix must keep that
65
+ # pre-existing convention and just expand the single name to every alias.
66
+ def test_title_lists_every_alias
67
+ assert_equal('Enumerable2#collect, Enumerable2#map',
68
+ title_of(method_html('Enumerable2#collect')))
69
+ end
70
+
71
+ # Both aliases are the very same entry, so they share one page/title
72
+ # regardless of which alias URL was requested.
73
+ def test_title_is_the_same_regardless_of_which_alias_was_requested
74
+ assert_equal(method_html('Enumerable2#collect'), method_html('Enumerable2#map'))
75
+ end
76
+
77
+ # A method without any alias renders exactly as before: only its own name.
78
+ def test_title_is_unchanged_for_a_method_without_aliases
79
+ html = method_html('Enumerable2#select')
80
+ assert_equal('Enumerable2#select', title_of(html))
81
+ end
82
+
83
+ # Only the <title> tag changes; the on-page h1 headline keeps showing only
84
+ # the entry's own (alphabetically-first) name, as before.
85
+ def test_h1_headline_is_not_affected
86
+ html = method_html('Enumerable2#collect')
87
+ assert_match(%r{<h1>instance method Enumerable2\#collect</h1>}, html)
88
+ assert_not_match(/<h1>[^<]*Enumerable2#map/, html)
89
+ end
90
+ end
91
+
92
+ # bitclust#132 P3: since/until バージョンバッジが method ページ(template.offline、
93
+ # statichtml/chm が使う経路)の <dt> に実際に描画されることのエンドツーエンド確認
94
+ class TestMethodScreenVersionBadges < Test::Unit::TestCase
95
+ SRC = <<'HERE'
96
+ = class Enumerable2 < Object
97
+ enumerable class
98
+ == Instance Methods
99
+ --- select -> Array
100
+
101
+ 説明
102
+ HERE
103
+
104
+ def setup
105
+ @lib, @db = BitClust::RRDParser.parse(SRC, 'testlib')
106
+ datadir = File.expand_path('../data/bitclust', __dir__)
107
+ @manager = BitClust::ScreenManager.new(
108
+ :templatedir => "#{datadir}/template.offline",
109
+ :catalogdir => "#{datadir}/catalog",
110
+ :encoding => 'utf-8',
111
+ :default_encoding => 'utf-8',
112
+ :base_url => '',
113
+ :target_version => '3.4'
114
+ )
115
+ end
116
+
117
+ def test_since_badge_is_rendered_after_fill_since
118
+ entry = @db.get_method(BitClust::MethodSpec.parse('Enumerable2#select'))
119
+ entry.fill_since('select', '3.2')
120
+ html = @manager.method_screen([entry], :database => @db).body
121
+ assert_include(html, '<span class="method-since-badge">Ruby 3.2 から</span>')
122
+ end
123
+ end
124
+
125
+ # bitclust#250: Ruby 4.0 以降のドキュメントでは module function の表記を
126
+ # 独自の「.#」から「?.」に変える(表示のみ、識別子は変えない)。この
127
+ # メソッドページの見出し・<title> がまさに issue に挙がっている
128
+ # 「module function Kernel.#open」の例そのもの
129
+ class TestMethodScreenModuleFunctionDisplay < Test::Unit::TestCase
130
+ SRC = <<'HERE'
131
+ = module Kernel
132
+ description
133
+ == Module Functions
134
+ --- mf
135
+
136
+ 説明
137
+ HERE
138
+
139
+ def render(version)
140
+ _lib, db = BitClust::RRDParser.parse(SRC, 'testlib', {'version' => version})
141
+ entry = db.get_method(BitClust::MethodSpec.parse('Kernel.#mf'))
142
+ datadir = File.expand_path('../data/bitclust', __dir__)
143
+ manager = BitClust::ScreenManager.new(
144
+ :templatedir => "#{datadir}/template.offline",
145
+ :catalogdir => "#{datadir}/catalog",
146
+ :encoding => 'utf-8',
147
+ :default_encoding => 'utf-8',
148
+ :base_url => '',
149
+ :target_version => version
150
+ )
151
+ manager.method_screen([entry], :database => db).body
152
+ end
153
+
154
+ def test_headline_and_title_show_dot_hash_before_4_0
155
+ html = render('3.4')
156
+ assert_match(%r{<h1>module function Kernel\.#mf</h1>}, html)
157
+ assert_include(html, '<title>Kernel.#mf')
158
+ end
159
+
160
+ def test_headline_and_title_switch_to_question_dot_at_4_0
161
+ html = render('4.0')
162
+ assert_match(%r{<h1>module function Kernel\?\.mf</h1>}, html)
163
+ assert_include(html, '<title>Kernel?.mf')
164
+ assert_not_include(html, 'Kernel.#mf')
165
+ end
166
+ end
@@ -0,0 +1,290 @@
1
+ # frozen_string_literal: true
2
+ require 'test/unit'
3
+ require 'bitclust'
4
+ require 'bitclust/methoddatabase'
5
+ require 'bitclust/method_since_calculator'
6
+ require 'tmpdir'
7
+ require 'fileutils'
8
+
9
+ # メソッド名別 since/until の算出(bitclust#132 P2)。
10
+ #
11
+ # テストリスト:
12
+ # [x] フロア(ラダー最古版から存在)のメソッドは since も until も付かない
13
+ # [x] #@since ゲート付きメソッドは、追加された版以降の対象すべてで
14
+ # since が付く
15
+ # [x] 別名グループ: シグネチャごとに異なるゲートを持てば名前ごとに
16
+ # 別の since になる(-@ は 2.0.0、dedup は 3.0)。ゲートが効いて
17
+ # いない版の対象ではその別名自体が存在しない
18
+ # [x] #@until ゲート付きメソッドは、消える1つ前の版の対象で until が付き、
19
+ # 消えた後の対象にはエントリ自体が存在しない
20
+ # [x] インスタンスメソッドとシンゲルトンメソッドは同名でも別キー
21
+ # [x] kind=:undefined のエントリは走査で無視される: {: undef} → 実メソッド化
22
+ # したものは実メソッド化した版が since になり、逆に undef 化は削除扱いで
23
+ # 前の版の対象に until が付く
24
+ # [x] kind=:undefined のエントリには適用でも書き込まない
25
+ # [x] ラダーに同じバージョンが重複していれば .new で UserError
26
+ # [x] apply は冪等: 2回目は floor_skipped 以外すべて0
27
+ # [x] 既に値がある名前は上書きしない(著者値が算出値より優先)
28
+ # [x] 対象DBのバージョンがラダーに無ければ apply は UserError
29
+ # [x] 統合: `{: since="X"}` 属性行(#132 P4)でパース時に記録された値は
30
+ # 算出結果で上書きされない(RD ソース上の明示注記が算出値より優先)
31
+ class TestMethodSinceCalculator < Test::Unit::TestCase
32
+ RD = <<~'RD'
33
+ description
34
+
35
+ = class MSCTest
36
+ == Instance Methods
37
+ --- old_timer
38
+
39
+ 説明
40
+
41
+ #@since 2.0.0
42
+ --- newmeth
43
+
44
+ 説明
45
+ #@end
46
+
47
+ #@since 3.0
48
+ --- overridden
49
+ {: since="2.5"}
50
+
51
+ 説明
52
+ #@end
53
+
54
+ #@since 2.0.0
55
+ --- suppressed
56
+ {: since=""}
57
+
58
+ 説明
59
+ #@end
60
+
61
+ #@since 2.0.0
62
+ --- -@
63
+ #@since 3.0
64
+ --- dedup
65
+ #@end
66
+
67
+ 説明
68
+ #@end
69
+
70
+ #@until 3.0
71
+ --- legacy
72
+
73
+ 説明
74
+ #@end
75
+
76
+ --- undefmeth
77
+ {: undef}
78
+
79
+ 説明
80
+
81
+ --- undef_then_real
82
+ #@until 2.0.0
83
+ {: undef}
84
+ #@end
85
+
86
+ 説明
87
+
88
+ #@since 2.0.0
89
+ --- real_then_undef
90
+ #@since 3.0
91
+ {: undef}
92
+ #@end
93
+
94
+ 説明
95
+ #@end
96
+
97
+ --- marker
98
+
99
+ 説明
100
+
101
+ == Singleton Methods
102
+ #@since 2.0.0
103
+ --- marker
104
+
105
+ 説明
106
+ #@end
107
+ RD
108
+
109
+ LADDER_VERSIONS = %w[1.8.7 2.0.0 3.0]
110
+
111
+ def setup
112
+ @tmpdir = Dir.mktmpdir
113
+ @paths = {}
114
+ LADDER_VERSIONS.each {|v| build_db(v) }
115
+ end
116
+
117
+ def teardown
118
+ FileUtils.rm_rf(@tmpdir)
119
+ end
120
+
121
+ def test_floor_method_has_no_since_or_until
122
+ run_calculator('2.0.0')
123
+ run_calculator('3.0')
124
+ assert_nil find_entry('2.0.0', 'i', 'old_timer').since_of('old_timer')
125
+ assert_nil find_entry('2.0.0', 'i', 'old_timer').until_of('old_timer')
126
+ assert_nil find_entry('3.0', 'i', 'old_timer').since_of('old_timer')
127
+ assert_nil find_entry('3.0', 'i', 'old_timer').until_of('old_timer')
128
+ end
129
+
130
+ def test_since_gated_method_gets_since_in_every_later_target
131
+ run_calculator('2.0.0')
132
+ run_calculator('3.0')
133
+ assert_equal '2.0.0', find_entry('2.0.0', 'i', 'newmeth').since_of('newmeth')
134
+ assert_equal '2.0.0', find_entry('3.0', 'i', 'newmeth').since_of('newmeth')
135
+ end
136
+
137
+ def test_alias_group_since_is_split_by_name
138
+ run_calculator('2.0.0')
139
+ run_calculator('3.0')
140
+
141
+ at_2_0_0 = find_entry('2.0.0', 'i', '-@')
142
+ assert_equal ['-@'], at_2_0_0.names
143
+ assert_equal '2.0.0', at_2_0_0.since_of('-@')
144
+
145
+ at_3_0 = find_entry('3.0', 'i', '-@')
146
+ assert_equal ['-@', 'dedup'], at_3_0.names.sort
147
+ assert_equal '2.0.0', at_3_0.since_of('-@')
148
+ assert_equal '3.0', at_3_0.since_of('dedup')
149
+ end
150
+
151
+ def test_until_gated_method
152
+ run_calculator('2.0.0')
153
+ assert_equal '3.0', find_entry('2.0.0', 'i', 'legacy').until_of('legacy')
154
+ assert_nil find_entry('3.0', 'i', 'legacy')
155
+ end
156
+
157
+ def test_singleton_and_instance_method_are_distinct_keys
158
+ run_calculator('2.0.0')
159
+ assert_nil find_entry('2.0.0', 'i', 'marker').since_of('marker')
160
+ assert_equal '2.0.0', find_entry('2.0.0', 's', 'marker').since_of('marker')
161
+ end
162
+
163
+ def test_undefined_entries_are_ignored_in_scan
164
+ run_calculator('3.0')
165
+ # 全版 {: undef} の undefmeth は presence ゼロ → 何も書かれない
166
+ e = find_entry('3.0', 'i', 'undefmeth')
167
+ assert_equal :undefined, e.kind
168
+ assert_nil e.since_of('undefmeth')
169
+ assert_nil e.until_of('undefmeth')
170
+ # 1.8.7 では {: undef}(存在しない印)なので presence に数えず、実メソッド化
171
+ # した 2.0.0 が since になる(scan がフィルタしなければ floor 扱いで nil)
172
+ assert_equal '2.0.0',
173
+ find_entry('3.0', 'i', 'undef_then_real').since_of('undef_then_real')
174
+ end
175
+
176
+ def test_undefined_entries_are_ignored_in_apply
177
+ run_calculator('2.0.0')
178
+ run_calculator('3.0')
179
+ # 3.0 で undef 化 → presence は 2.0.0 のみ。2.0.0 の対象では削除扱いで
180
+ # until が付く
181
+ at_2_0_0 = find_entry('2.0.0', 'i', 'real_then_undef')
182
+ assert_equal '2.0.0', at_2_0_0.since_of('real_then_undef')
183
+ assert_equal '3.0', at_2_0_0.until_of('real_then_undef')
184
+ # 3.0 の :undefined エントリ自体には(算出値があっても)書き込まない
185
+ at_3_0 = find_entry('3.0', 'i', 'real_then_undef')
186
+ assert_equal :undefined, at_3_0.kind
187
+ assert_nil at_3_0.since_of('real_then_undef')
188
+ assert_nil at_3_0.until_of('real_then_undef')
189
+ end
190
+
191
+ def test_duplicate_version_in_ladder_raises
192
+ dbs = ladder_dbs + [fresh_db('2.0.0')]
193
+ assert_raise(BitClust::UserError) { BitClust::MethodSinceCalculator.new(dbs) }
194
+ end
195
+
196
+ def test_apply_is_idempotent
197
+ calc = BitClust::MethodSinceCalculator.new(ladder_dbs)
198
+ calc.scan
199
+ first_stats = calc.apply(fresh_db('2.0.0'))
200
+ second_stats = calc.apply(fresh_db('2.0.0'))
201
+
202
+ assert_operator first_stats[:since_filled], :>, 0
203
+ assert_equal 0, second_stats[:since_filled]
204
+ assert_equal 0, second_stats[:until_filled]
205
+ assert_equal 0, second_stats[:entries_updated]
206
+ assert_equal first_stats[:floor_skipped], second_stats[:floor_skipped]
207
+ end
208
+
209
+ def test_author_value_takes_precedence_over_computed_value
210
+ pre = fresh_db('3.0')
211
+ entry = pre.get_class('MSCTest').entries.find {|m| m.typechar == 'i' && m.name?('newmeth') } or raise
212
+ entry.fill_since('newmeth', '1.9.9')
213
+ entry.save
214
+
215
+ calc = BitClust::MethodSinceCalculator.new(ladder_dbs)
216
+ calc.scan
217
+ calc.apply(fresh_db('3.0'))
218
+
219
+ assert_equal '1.9.9', find_entry('3.0', 'i', 'newmeth').since_of('newmeth')
220
+ end
221
+
222
+ def test_author_since_attribute_parsed_from_source_survives_apply
223
+ # overridden は #@since 3.0 でゲートされているのでラダー上は 3.0 と算出
224
+ # されるが、RD ソースの {: since="2.5"} がパース時点(update_by_stdlibtree)
225
+ # で既に記録されているため、apply(fill_since は未設定時のみ書く)では
226
+ # 上書きされずそのまま残る
227
+ run_calculator('2.0.0')
228
+ run_calculator('3.0')
229
+
230
+ assert_nil find_entry('2.0.0', 'i', 'overridden')
231
+ assert_equal '2.5', find_entry('3.0', 'i', 'overridden').since_of('overridden')
232
+ end
233
+
234
+ def test_explicit_empty_since_suppresses_computed_value
235
+ # suppressed は #@since 2.0.0 ゲートによりラダー上は 2.0.0 と算出されるが、
236
+ # {: since=""}(明示的に不明=バッジ非表示)がパース時に記録されているため
237
+ # apply は上書きしない(bitclust#132: ドキュメント追加がメソッド追加より
238
+ # 遅れて算出が誤る場合の抑止)
239
+ run_calculator('3.0')
240
+ assert_equal '', find_entry('3.0', 'i', 'suppressed').since_of('suppressed')
241
+ end
242
+
243
+ def test_apply_rejects_target_whose_version_is_not_in_ladder
244
+ build_db('4.0')
245
+ calc = BitClust::MethodSinceCalculator.new(ladder_dbs)
246
+ calc.scan
247
+ assert_raise(BitClust::UserError) { calc.apply(fresh_db('4.0')) }
248
+ end
249
+
250
+ private
251
+
252
+ def build_db(version)
253
+ root = "#{@tmpdir}/tree-#{version}/refm/api/src"
254
+ FileUtils.mkdir_p("#{root}/_builtin")
255
+ File.write("#{root}/LIBRARIES", "_builtin\n")
256
+ File.write("#{root}/_builtin.rd", RD)
257
+ prefix = "#{@tmpdir}/db-#{version}"
258
+ db = BitClust::MethodDatabase.new(prefix)
259
+ db.init
260
+ db.transaction do
261
+ db.propset('version', version)
262
+ db.propset('encoding', 'utf-8')
263
+ end
264
+ db.transaction do
265
+ db.update_by_stdlibtree(root)
266
+ end
267
+ @paths[version] = prefix
268
+ prefix
269
+ end
270
+
271
+ def fresh_db(version)
272
+ BitClust::MethodDatabase.new(@paths[version] || raise("no such db: #{version}"))
273
+ end
274
+
275
+ def ladder_dbs
276
+ LADDER_VERSIONS.map {|v| fresh_db(v) }
277
+ end
278
+
279
+ def run_calculator(target_version)
280
+ calc = BitClust::MethodSinceCalculator.new(ladder_dbs)
281
+ calc.scan
282
+ calc.apply(fresh_db(target_version))
283
+ end
284
+
285
+ def find_entry(version, typechar, name)
286
+ fresh_db(version).get_class('MSCTest').entries.find {|m|
287
+ m.typechar == typechar && m.name?(name)
288
+ }
289
+ end
290
+ end
@@ -57,6 +57,22 @@ class TestMethodDatabase < Test::Unit::TestCase
57
57
  @db.get_class("B").dynamically_included.map{|m| m.name})
58
58
  end
59
59
 
60
+ # source_location must be stored without a line number so that regenerating
61
+ # the database does not churn the persisted entries (and the generated HTML).
62
+ def test_source_location_serialized_without_line_number
63
+ entry = @db.search_methods(BitClust::MethodNamePattern.new(nil, nil, 'at_exit')).records.first.entry
64
+ entry.source_location = BitClust::Location.new('refm/api/src/_builtin/Kernel', 42)
65
+ assert_equal('refm/api/src/_builtin/Kernel', entry._get_properties['source_location'])
66
+ end
67
+
68
+ def test_source_location_roundtrip_drops_line_number
69
+ entry = @db.search_methods(BitClust::MethodNamePattern.new(nil, nil, 'at_exit')).records.first.entry
70
+ entry.source_location = BitClust::Location.new('refm/api/src/_builtin/Kernel', 42)
71
+ entry._set_properties(entry._get_properties)
72
+ assert_equal('refm/api/src/_builtin/Kernel', entry.source_location.file)
73
+ assert_nil(entry.source_location.line)
74
+ end
75
+
60
76
  private
61
77
  def setup_files
62
78
  FileUtils.mkdir_p("#{@root}/_builtin")