From 80040c166e5f53bdbbaf57dbb5e1d282d715d434 Mon Sep 17 00:00:00 2001 From: shyouhei Date: Mon, 23 Jan 2017 02:47:16 +0000 Subject: improve C0 coverage of insns.def from 65.9% to 96.1% While I was developing my private topic branch I found that the VM itself is not tested very much in `make test` tests. Of course `make test-all` covers vast majority of the VM but running that task is not an immediately possible thing when we are touching the VM. In order to boost development in a rapid cycle I decided to add some tests to the bootstraptest. Here it is. * test_insns.rb: new test that covers insns.def. * runner.rb (#assert_equal): pass extra options to the target so that we can test frozen_string_literal: true situation. git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@57399 b2dd03c8-39d4-4d8f-98ff-823fe69b080e --- bootstraptest/runner.rb | 15 +- bootstraptest/test_insns.rb | 391 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 399 insertions(+), 7 deletions(-) create mode 100644 bootstraptest/test_insns.rb diff --git a/bootstraptest/runner.rb b/bootstraptest/runner.rb index 754c3f0f3b..110b00d658 100755 --- a/bootstraptest/runner.rb +++ b/bootstraptest/runner.rb @@ -267,17 +267,17 @@ def nacl? @ruby and File.basename(@ruby.split(/\s/).first)['sel_ldr'] end -def assert_check(testsrc, message = '', opt = '') +def assert_check(testsrc, message = '', opt = '', **argh) show_progress(message) { - result = get_result_string(testsrc, opt) + result = get_result_string(testsrc, opt, **argh) check_coredump yield(result) } end -def assert_equal(expected, testsrc, message = '') +def assert_equal(expected, testsrc, message = '', opt = '', **argh) newtest - assert_check(testsrc, message) {|result| + assert_check(testsrc, message, opt, **argh) {|result| if expected == result nil else @@ -419,18 +419,19 @@ def untabify(str) str.gsub(/^\t+/) {' ' * (8 * $&.size) } end -def make_srcfile(src) +def make_srcfile(src, frozen_string_literal: nil) filename = 'bootstraptest.tmp.rb' File.open(filename, 'w') {|f| + f.puts "#frozen_string_literal:true" if frozen_string_literal f.puts "GC.stress = true" if $stress f.puts "print(begin; #{src}; end)" } filename end -def get_result_string(src, opt = '') +def get_result_string(src, opt = '', **argh) if @ruby - filename = make_srcfile(src) + filename = make_srcfile(src, **argh) begin `#{@ruby} -W0 #{opt} #{filename}` ensure diff --git a/bootstraptest/test_insns.rb b/bootstraptest/test_insns.rb new file mode 100644 index 0000000000..bafa5e85a1 --- /dev/null +++ b/bootstraptest/test_insns.rb @@ -0,0 +1,391 @@ +# C0 coverage of each instructions + +# :NOTE: This is for development purpose; never consider this file as +# ISeq compilation specification. + +begin + # This library brings some additional coverage. + # Not mandatory. + require 'rbconfig/sizeof' +rescue LoadError + # OK, just skip +else + bits = 8 * RbConfig::SIZEOF['long'] + $LONG_MAX = (1 << (bits - 1)) - 1 + $LONG_MIN = -1 * $LONG_MAX - 1 + $FIXNUM_MAX = $LONG_MAX >> 1 + $FIXNUM_MIN = $LONG_MIN >> 1 +end + +fsl = { frozen_string_literal: true } # used later +tests = [ + # insn , expression to generate such insn + [ 'nop', %q{ raise rescue true }, ], + [ 'trace', %q{ true }, ], + + [ 'setlocal *, 0', %q{ x = true }, ], + [ 'setlocal *, 1', %q{ x = nil; -> { x = true }.call }, ], + [ 'setlocal', %q{ x = nil; -> { -> { x = true }.() }.() }, ], + [ 'getlocal *, 0', %q{ x = true; x }, ], + [ 'getlocal *, 1', %q{ x = true; -> { x }.call }, ], + [ 'getlocal', %q{ x = true; -> { -> { x }.() }.() }, ], + + [ 'setspecial', %q{ true if true..true }, ], + [ 'getspecial', %q{ $&.nil? }, ], + [ 'getspecial', %q{ $`.nil? }, ], + [ 'getspecial', %q{ $'.nil? }, ], + [ 'getspecial', %q{ $+.nil? }, ], + [ 'getspecial', %q{ $1.nil? }, ], + [ 'getspecial', %q{ $128.nil? }, ], + + [ 'getglobal', %q{ String === $0 }, ], + [ 'getglobal', %q{ $_.nil? }, ], + [ 'setglobal', %q{ $0 = "true" }, ], + + [ 'setinstancevariable', %q{ @x = true }, ], + [ 'getinstancevariable', %q{ @x = true; @x }, ], + + [ 'setclassvariable', %q{ @@x = true }, ], + [ 'getclassvariable', %q{ @@x = true; @@x }, ], + + [ 'setconstant', %q{ X = true }, ], + [ 'setconstant', %q{ Object::X = true }, ], + [ 'getconstant', %q{ X = true; X }, ], + [ 'getconstant', %q{ X = true; Object::X }, ], + + [ 'getinlinecache / setinlinecache', %q{ def x; X; end; X = true; x; x; x }, ], + + [ 'putnil', %q{ $~ == nil }, ], + [ 'putself', %q{ $~ != self }, ], + [ 'putobject INT2FIX(0)', %q{ $~ != 0 }, ], + [ 'putobject INT2FIX(1)', %q{ $~ != 1 }, ], + [ 'putobject', %q{ $~ != -1 }, ], + [ 'putobject', %q{ $~ != /x/ }, ], + [ 'putobject', %q{ $~ != :x }, ], + [ 'putobject', %q{ $~ != (1..2) }, ], + [ 'putobject', %q{ $~ != true }, ], + [ 'putobject', %q{ /(?x)/ =~ "x"; x == "x" }, ], + + [ 'putspecialobject', %q{ {//=>true}[//] }, ], + [ 'putiseq', %q{ -> { true }.() }, ], + [ 'putstring', %q{ "true" }, ], + [ 'tostring / concatstrings', %q{ "#{true}" }, ], + [ 'freezestring', %q{ "#{true}"}, fsl, ], + [ 'freezestring', %q{ "#{true}"}, '-d', fsl, ], + [ 'torexp', %q{ /#{true}/ =~ "true" && $~ }, ], + + [ 'newarray', %q{ ["true"][0] }, ], + [ 'duparray', %q{ [ true ][0] }, ], + [ 'expandarray', %q{ y = [ true, false, nil ]; x, = y; x }, ], + [ 'expandarray', %q{ y = [ true, false, nil ]; x, *z = y; x }, ], + [ 'expandarray', %q{ y = [ true, false, nil ]; x, *z, w = y; x }, ], + [ 'splatarray', %q{ x, = *(y = true), false; x }, ], + [ 'concatarray', %q{ ["t", "r", *x = "u", "e"].join }, ], + [ 'concatarray', <<~'},', ], # { + class X; def to_a; ['u']; end; end + ['t', 'r', *X.new, 'e'].join + }, + [ 'concatarray', <<~'},', ], # { + r = false + t = [true, nil] + q, w, e = r, *t # here + w + }, + + [ 'newhash', %q{ x = {}; x[x] = true }, ], + [ 'newhash', %q{ x = true; { x => x }[x] }, ], + [ 'newrange', %q{ x = 1; [*(0..x)][0] == 0 }, ], + [ 'newrange', %q{ x = 1; [*(0...x)][0] == 0 }, ], + + [ 'pop', %q{ def x; true; end; x }, ], + [ 'dup', %q{ x = y = true; x }, ], + [ 'dupn', %q{ Object::X ||= true }, ], + [ 'dupn', %q{ Object::X ||= true }, ], + [ 'reverse', %q{ q, (w, e), r = 1, [2, 3], 4; e == 3 }, ], + [ 'swap', <<~'},', ], # { + x = [[false, true]] + for i, j in x # here + ; + end + j + }, + + [ 'topn', %q{ x, y = [], 0; x[*y], = [true, false]; x[0] }, ], + [ 'setn', %q{ x, y = [], 0; x[*y] = true ; x[0] }, ], + [ 'adjuststack', %q{ x = [true]; x[0] ||= nil; x[0] }, ], + + [ 'defined', %q{ !defined?(x) }, ], + [ 'checkkeyword', %q{ def x x:rand;x end; x x: true }, ], + [ 'checkmatch', <<~'},', ], # { + x = y = true + case x + when false + y = false + when true # here + y = nil + end + y == nil + }, + [ 'checkmatch', <<~'},', ], # { + x, y = true, [false] + case x + when *y # here + z = false + else + z = true + end + z + }, + [ 'checkmatch', <<~'},', ], # { + x = false + begin + raise + rescue # here + x = true + end + x + }, + + [ 'defineclass', %q{ module X; true end }, ], + [ 'defineclass', %q{ X = Module.new; module X; true end }, ], + [ 'defineclass', %q{ class X; true end }, ], + [ 'defineclass', %q{ X = Class.new; class X; true end }, ], + [ 'defineclass', %q{ X = Class.new; class Y < X; true end }, ], + [ 'defineclass', %q{ X = Class.new; class << X; true end }, ], + [ 'defineclass', <<~'},', ], # { + X = Class.new + Y = Class.new(X) + class Y < X + true + end + }, + + [ 'opt_send_without_block', %q{ true.to_s }, ], + [ 'send', %q{ true.tap {|i| i.to_s } }, ], + [ 'leave', %q{ def x; true; end; x }, ], + [ 'invokesuper', <<~'},', ], # { + class X < String + def empty? + super # here + end + end + X.new.empty? + }, + [ 'invokeblock', <<~'},', ], # { + def x + return yield self # here + end + x do + true + end + }, + + [ 'opt_str_freeze', %q{ 'true'.freeze }, ], + [ 'opt_str_freeze', <<~'},', ], # { + class String + def freeze + true + end + end + 'true'.freeze + }, + + [ 'opt_newarray_max', %q{ [ ].max.nil? }, ], + [ 'opt_newarray_max', %q{ [1, x = 2, 3].max == 3 }, ], + [ 'opt_newarray_max', <<~'},', ], # { + class Array + def max + true + end + end + [1, x = 2, 3].max + }, + [ 'opt_newarray_min', %q{ [ ].min.nil? }, ], + [ 'opt_newarray_min', %q{ [3, x = 2, 1].min == 1 }, ], + [ 'opt_newarray_min', <<~'},', ], # { + class Array + def min + true + end + end + [3, x = 2, 1].min + }, + + [ 'throw', %q{ false.tap { break true } }, ], + [ 'branchif', %q{ x = nil; x ||= true }, ], + [ 'branchif', %q{ x = true; x ||= nil; x }, ], + [ 'branchunless', %q{ x = 1; x &&= true }, ], + [ 'branchunless', %q{ x = nil; x &&= true; x.nil? }, ], + [ 'branchnil', %q{ x = true; x&.to_s }, ], + [ 'branchnil', %q{ x = nil; (x&.to_s).nil? }, ], + [ 'jump', <<~'},', ], # { + y = 1 + x = if y == 0 then nil elsif y == 1 then true else nil end + x + }, + [ 'jump', <<~'},', ], # { + # ultra complicated situation: this ||= assinment only generates + # 15 instructions, not including the class definition. + class X; attr_accessor :x; end + x = X.new + x&.x ||= true # here + }, + + [ 'once', %q{ /#{true}/o =~ "true" && $~ }, ], + [ 'once', <<~'},', ], # { + def once expr + return /#{expr}/o # here + end + x = once(true); x = once(false); x = once(nil); + x =~ "true" && $~ + }, + [ 'once', <<~'},', ], # { + # recursive once + def once n + return %r/#{ + if n == 0 + true + else + once(n-1) # here + end + }/ox + end + x = once(128); x = once(7); x = once(16); + x =~ "true" && $~ + }, + [ 'once', <<~'},', ], # { + # inter-thread lockup situation + def once n + return Thread.start n do |m| + Thread.pass + next %r/#{ + sleep m # here + true + }/ox + end + end + x = once(1); y = once(0.1); z = y.value + z =~ "true" && $~ + }, + + [ 'opt_case_dispatch', %q{ case 0 when 1.1 then false else true end }, ], + [ 'opt_case_dispatch', %q{ case 1.0 when 1.1 then false else true end }, ], + + [ 'opt_plus', %q{ 1 + 1 == 2 }, ], + if defined? $LONG_MAX then + [ 'opt_plus', %Q{ #{ $FIXNUM_MAX } + 1 == #{ $FIXNUM_MAX + 1 } }, ] + end, + [ 'opt_plus', %q{ 1.0 + 1.0 == 2.0 }, ], + [ 'opt_plus', %q{ x = +0.0.next_float; x + x >= x }, ], + [ 'opt_plus', %q{ 't' + 'rue' }, ], + [ 'opt_plus', %q{ ( ['t'] + ['r', ['u', ['e'], ], ] ).join }, ], + [ 'opt_plus', %q{ Time.at(1) + 1 == Time.at(2) }, ], + [ 'opt_minus', %q{ 1 - 1 == 0 }, ], + if defined? $LONG_MAX then + [ 'opt_minus', %Q{ #{ $FIXNUM_MIN } - 1 == #{ $FIXNUM_MIN - 1 } }, ] + end, + [ 'opt_minus', %q{ 1.0 - 1.0 == 0.0 }, ], + [ 'opt_minus', %q{ x = -0.0.prev_float; x - x == 0.0 }, ], + [ 'opt_minus', %q{ ( [false, true] - [false] )[0] }, ], + [ 'opt_mult', %q{ 1 * 1 == 1 }, ], + [ 'opt_mult', %q{ 1.0 * 1.0 == 1.0 }, ], + [ 'opt_mult', %q{ x = +0.0.next_float; x * x <= x }, ], + [ 'opt_mult', %q{ ( "ruet" * 3 )[7,4] }, ], + [ 'opt_div', %q{ 1 / 1 == 1 }, ], + [ 'opt_div', %q{ 1.0 / 1.0 == 1.0 }, ], + [ 'opt_div', %q{ x = +0.0.next_float; x / x >= x }, ], + [ 'opt_div', %q{ x = 1/2r; x / x == 1 }, ], + [ 'opt_mod', %q{ 1 % 1 == 0 }, ], + [ 'opt_mod', %q{ 1.0 % 1.0 == 0.0 }, ], + [ 'opt_mod', %q{ x = +0.0.next_float; x % x == 0.0 }, ], + [ 'opt_mod', %q{ '%s' % [ true ] }, ], + + [ 'opt_eq', %q{ 1 == 1 }, ], + [ 'opt_eq', <<~'},', ], # { + class X; def == other; true; end; end + X.new == true + }, + [ 'opt_neq', %q{ 1 != 0 }, ], + [ 'opt_neq', <<~'},', ], # { + class X; def != other; true; end; end + X.new != true + }, + + [ 'opt_lt', %q{ -1 < 0 }, ], + [ 'opt_lt', %q{ -1.0 < 0.0 }, ], + [ 'opt_lt', %q{ -0.0.prev_float < 0.0 }, ], + [ 'opt_lt', %q{ ?a < ?z }, ], + [ 'opt_le', %q{ -1 <= 0 }, ], + [ 'opt_le', %q{ -1.0 <= 0.0 }, ], + [ 'opt_le', %q{ -0.0.prev_float <= 0.0 }, ], + [ 'opt_le', %q{ ?a <= ?z }, ], + [ 'opt_gt', %q{ 1 > 0 }, ], + [ 'opt_gt', %q{ 1.0 > 0.0 }, ], + [ 'opt_gt', %q{ +0.0.next_float > 0.0 }, ], + [ 'opt_gt', %q{ ?z > ?a }, ], + [ 'opt_ge', %q{ 1 >= 0 }, ], + [ 'opt_ge', %q{ 1.0 >= 0.0 }, ], + [ 'opt_ge', %q{ +0.0.next_float >= 0.0 }, ], + [ 'opt_ge', %q{ ?z >= ?a }, ], + + [ 'opt_ltlt', %q{ '' << 'true' }, ], + [ 'opt_ltlt', %q{ ([] << 'true').join }, ], + [ 'opt_ltlt', %q{ (1 << 31) == 2147483648 }, ], + + [ 'opt_aref', %q{ ['true'][0] }, ], + [ 'opt_aref', %q{ { 0 => 'true'}[0] }, ], + [ 'opt_aref', %q{ 'true'[0] == ?t }, ], + [ 'opt_aset', %q{ [][0] = true }, ], + [ 'opt_aset', %q{ {}[0] = true }, ], + [ 'opt_aset', %q{ x = 'frue'; x[0] = 't'; x }, ], + [ 'opt_aset', <<~'},', ], # { + # opt_aref / opt_aset mixup situation + class X; def x; {}; end; end + x = X.new + x&.x[true] ||= true # here + }, + + [ 'opt_aref_with', %q{ { 'true' => true }['true'] }, ], + [ 'opt_aref_with', %q{ Struct.new(:nil).new['nil'].nil? }, ], + [ 'opt_aset_with', %q{ {}['true'] = true }, ], + [ 'opt_aset_with', %q{ Struct.new(:true).new['true'] = true }, ], + + [ 'opt_length', %q{ 'true' .length == 4 }, ], + [ 'opt_length', %q{ :true .length == 4 }, ], + [ 'opt_length', %q{ [ 'true' ] .length == 1 }, ], + [ 'opt_length', %q{ { 'true' => 1 }.length == 1 }, ], + [ 'opt_size', %q{ 'true' .size == 4 }, ], + [ 'opt_size', %q{ 1.size >= 4 }, ], + [ 'opt_size', %q{ [ 'true' ] .size == 1 }, ], + [ 'opt_size', %q{ { 'true' => 1 }.size == 1 }, ], + [ 'opt_empty_p', %q{ ''.empty? }, ], + [ 'opt_empty_p', %q{ [].empty? }, ], + [ 'opt_empty_p', %q{ {}.empty? }, ], + [ 'opt_empty_p', %q{ Queue.new.empty? }, ], + + [ 'opt_succ', %q{ 1.succ == 2 }, ], + if defined? $LONG_MAX then + [ 'opt_succ',%Q{ #{ $FIXNUM_MAX }.succ == #{ $FIXNUM_MAX + 1 } }, ] + end, + [ 'opt_succ', %q{ '1'.succ == '2' }, ], + [ 'opt_succ', %q{ x = Time.at(0); x.succ == Time.at(1) }, ], + + [ 'opt_not', %q{ ! false }, ], + [ 'opt_neq', <<~'},', ], # { + class X; def !; true; end; end + ! X.new + }, + + [ 'opt_regexpmatch1', %q{ /true/ =~ 'true' && $~ }, ], + [ 'opt_regexpmatch1', <<~'},', ], # { + class Regexp; def =~ other; true; end; end + /true/ =~ 'true' + }, + [ 'opt_regexpmatch2', %q{ 'true' =~ /true/ && $~ }, ], + [ 'opt_regexpmatch2', <<~'},', ], # { + class String; def =~ other; true; end; end + 'true' =~ /true/ + }, +] + +tests.compact.each {|(insn, expr, *a)| assert_equal 'true', expr, insn, *a } -- cgit v1.2.3