From a7cb693849327064418191155f4e26f4df228112 Mon Sep 17 00:00:00 2001 From: James Harton Date: Tue, 31 Mar 2020 20:45:55 +1300 Subject: [PATCH] Add test coverage for a bunch of stuff. --- Gemfile | 2 + Gemfile.lock | 6 +++ lib/wag.rb | 5 +- lib/wag/export.rb | 8 +-- lib/wag/f32_instructions.rb | 2 +- lib/wag/f64_instructions.rb | 2 +- lib/wag/function.rb | 7 +-- lib/wag/function_type.rb | 33 ++++-------- lib/wag/global_instructions.rb | 4 +- lib/wag/i32_instructions.rb | 4 +- lib/wag/i64_instructions.rb | 6 +-- lib/wag/import.rb | 8 +-- lib/wag/instructable.rb | 10 +++- lib/wag/instruction.rb | 1 + lib/wag/instructions/base.rb | 2 +- lib/wag/instructions/i64/and.rb | 10 ++++ lib/wag/local_instructions.rb | 4 +- lib/wag/memory.rb | 14 ++--- lib/wag/memory_instructions.rb | 4 +- lib/wag/module.rb | 6 +-- lib/wag/table.rb | 20 ++----- lib/wag/type.rb | 4 +- lib/wag/types/function.rb | 28 ---------- spec/spec_helper.rb | 1 + spec/wag/export_spec.rb | 8 +-- spec/wag/function_spec.rb | 80 ++++++++++++++++++++++++++++ spec/wag/global_instructions_spec.rb | 31 +++++++++++ spec/wag/i32_instructions_spec.rb | 47 ++++++++++++++++ spec/wag/i64_instructions_spec.rb | 48 +++++++++++++++++ spec/wag/import_spec.rb | 8 +-- spec/wag/inflector_spec.rb | 10 ++++ spec/wag/instructable_spec.rb | 59 ++++++++++++++++++++ spec/wag/label_spec.rb | 31 +++++++++++ spec/wag/local_instructions_spec.rb | 31 +++++++++++ spec/wag/memory_instructions_spec.rb | 36 +++++++++++++ spec/wag/memory_spec.rb | 28 ++++++++++ spec/wag/param_spec.rb | 22 ++++++++ spec/wag/result_spec.rb | 13 +++++ spec/wag/table_spec.rb | 13 +++++ spec/wag/type_spec.rb | 63 ++++++++++++++++++++++ spec/wag/types/f32_spec.rb | 12 +++++ spec/wag/types/f64_spec.rb | 12 +++++ spec/wag/types/i32_spec.rb | 12 +++++ spec/wag/types/i64_spec.rb | 12 +++++ 44 files changed, 651 insertions(+), 116 deletions(-) create mode 100644 lib/wag/instructions/i64/and.rb delete mode 100644 lib/wag/types/function.rb create mode 100644 spec/wag/function_spec.rb create mode 100644 spec/wag/global_instructions_spec.rb create mode 100644 spec/wag/i32_instructions_spec.rb create mode 100644 spec/wag/i64_instructions_spec.rb create mode 100644 spec/wag/inflector_spec.rb create mode 100644 spec/wag/instructable_spec.rb create mode 100644 spec/wag/label_spec.rb create mode 100644 spec/wag/local_instructions_spec.rb create mode 100644 spec/wag/memory_instructions_spec.rb create mode 100644 spec/wag/memory_spec.rb create mode 100644 spec/wag/param_spec.rb create mode 100644 spec/wag/result_spec.rb create mode 100644 spec/wag/table_spec.rb create mode 100644 spec/wag/type_spec.rb create mode 100644 spec/wag/types/f32_spec.rb create mode 100644 spec/wag/types/f64_spec.rb create mode 100644 spec/wag/types/i32_spec.rb create mode 100644 spec/wag/types/i64_spec.rb diff --git a/Gemfile b/Gemfile index 6bfa264..ce5a1d8 100644 --- a/Gemfile +++ b/Gemfile @@ -9,3 +9,5 @@ gem 'pry', '~> 0.13.0' gem 'rake', '~> 12.0' gem 'rspec', '~> 3.0' gem 'rubocop', '~> 0.80.1' + +gem 'faker', '~> 2.11' diff --git a/Gemfile.lock b/Gemfile.lock index 08d1876..84bafa8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -9,8 +9,13 @@ GEM specs: ast (2.4.0) coderay (1.1.2) + concurrent-ruby (1.1.6) diff-lcs (1.3) dry-inflector (0.2.0) + faker (2.11.0) + i18n (>= 1.6, < 2) + i18n (1.8.2) + concurrent-ruby (~> 1.0) jaro_winkler (1.5.4) method_source (1.0.0) parallel (1.19.1) @@ -50,6 +55,7 @@ PLATFORMS ruby DEPENDENCIES + faker (~> 2.11) pry (~> 0.13.0) rake (~> 12.0) rspec (~> 3.0) diff --git a/lib/wag.rb b/lib/wag.rb index 778c9a3..e731437 100644 --- a/lib/wag.rb +++ b/lib/wag.rb @@ -15,10 +15,10 @@ module WAG require 'wag/export' require 'wag/f32_instructions' require 'wag/f64_instructions' - require 'wag/function_type' require 'wag/function' - require 'wag/global' + require 'wag/function_type' require 'wag/global_instructions' + require 'wag/global' require 'wag/i32_instructions' require 'wag/i64_instructions' require 'wag/import' @@ -26,6 +26,7 @@ module WAG require 'wag/instruction' require 'wag/label' require 'wag/local_instructions' + require 'wag/memory_instructions' require 'wag/memory' require 'wag/module' require 'wag/param' diff --git a/lib/wag/export.rb b/lib/wag/export.rb index 499a21c..e469081 100644 --- a/lib/wag/export.rb +++ b/lib/wag/export.rb @@ -20,14 +20,14 @@ module WAG @desc = Global.new(label, type) end - def memory(number, min: nil, max: nil, &block) - @desc = Memory.new(number, min: min, max: max) + def memory(number, min = nil, max = nil, &block) + @desc = Memory.new(number, min, max) @desc.instance_exec(&block) if block @desc end - def table(number, type = :funcref, min: nil, max: nil, &block) - @desc = Table.new(number, type, min: min, max: max) + def table(number, type = :funcref, &block) + @desc = Table.new(number, type) @desc.instance_exec(&block) if block @desc end diff --git a/lib/wag/f32_instructions.rb b/lib/wag/f32_instructions.rb index 648c0bb..5307be2 100644 --- a/lib/wag/f32_instructions.rb +++ b/lib/wag/f32_instructions.rb @@ -11,7 +11,7 @@ module WAG convert_i64_s convert_i64_u reinterpret_i32].each do |instruction_name| camelised_name = "F32#{WAG::Inflector.inflector.camelize(instruction_name)}" define_method(instruction_name) do |*args, &block| - instruction = WAG::Instruction.get_const(camelised_name).new(*args) + instruction = WAG::Instruction.const_get(camelised_name).new(*args) @instructions << instruction instruction.instance_exec(&block) if block instruction diff --git a/lib/wag/f64_instructions.rb b/lib/wag/f64_instructions.rb index 6c48d4b..d4be013 100644 --- a/lib/wag/f64_instructions.rb +++ b/lib/wag/f64_instructions.rb @@ -11,7 +11,7 @@ module WAG convert_i64_s convert_i64_u promote_f32 reinterpret_i64].each do |instruction_name| camelised_name = "F64#{WAG::Inflector.inflector.camelize(instruction_name)}" define_method(instruction_name) do |*args, &block| - instruction = WAG::Instruction.get_const(camelised_name).new(*args) + instruction = WAG::Instruction.const_get(camelised_name).new(*args) @instructions << instruction instruction.instance_exec(&block) if block instruction diff --git a/lib/wag/function.rb b/lib/wag/function.rb index b842c50..368fb7c 100644 --- a/lib/wag/function.rb +++ b/lib/wag/function.rb @@ -7,8 +7,8 @@ module WAG attr_reader :label, :params - def initialize(label) - @label = WAG::Label.from(label) + def initialize(label = nil) + @label = WAG::Label.from(label) if label @params = [] end @@ -23,7 +23,8 @@ module WAG end def to_sexpr - [:func, @label.to_sexpr].tap do |func| + [:func].tap do |func| + func.push(@label.to_sexpr) if @label func.concat(@params.map(&:to_sexpr)) unless @params.empty? func.push(@result.to_sexpr) if @result end diff --git a/lib/wag/function_type.rb b/lib/wag/function_type.rb index 3954ed5..d5bff43 100644 --- a/lib/wag/function_type.rb +++ b/lib/wag/function_type.rb @@ -1,31 +1,16 @@ # frozen_string_literal: true module WAG - class FunctionType - include WAG::WAT - - attr_reader :label, :params, :results - - def initialize(label) - @label = WAG::Label.from(label) - @params = [] - @results = [] - end - - def param(*types) - @params.concat(types.map { |t| WAG::Type.from(t) }) - end - - def result(*types) - @results.concat(types.map { |t| WAG::Type.from(t) }) - end - + class FunctionType < Function def to_sexpr - [:type, @label.to_sexpr, - [:func].tap do |func| - func.push([:param].concat(@params.map(&:to_sexpr))) if @params - func.push([:result].concat(@results.map(&:to_sexpr))) if @results - end] + [:type].tap do |type| + type.push(@label.to_sexpr) if @label + [:func].tap do |func| + func.concat(@params.map(&:to_sexpr)) unless @params.empty? + func.push(@result.to_sexpr) if @result + type.push(func) + end + end end end end diff --git a/lib/wag/global_instructions.rb b/lib/wag/global_instructions.rb index c704db8..3a1f3a0 100644 --- a/lib/wag/global_instructions.rb +++ b/lib/wag/global_instructions.rb @@ -7,9 +7,9 @@ module WAG end %i[get set].each do |instruction_name| - camelised_name = "Global#{WAG::Inflector.inflector.camelize(instruction_name)}" + camelised_name = "Global::#{WAG::Inflector.inflector.camelize(instruction_name)}" define_method(instruction_name) do |*args, &block| - instruction = WAG::Instruction.get_const(camelised_name).new(*args) + instruction = WAG::Instruction.const_get(camelised_name).new(*args) @instructions << instruction instruction.instance_exec(&block) if block instruction diff --git a/lib/wag/i32_instructions.rb b/lib/wag/i32_instructions.rb index 30c195a..5ea1624 100644 --- a/lib/wag/i32_instructions.rb +++ b/lib/wag/i32_instructions.rb @@ -10,9 +10,9 @@ module WAG ne lt_s lt_u gt_s gt_u le_s le_u ge_s ge_u clz ctz popcnt add sub mul div_s div_u rem_s rem_u and or xor shl shr_s shr_u rotl rotr trunc_f32_s trunc_f32_u trunc_f64_s trunc_f64_u reinterpret_f32].each do |instruction_name| - camelised_name = "I32#{WAG::Inflector.inflector.camelize(instruction_name)}" + camelised_name = "I32::#{WAG::Inflector.inflector.camelize(instruction_name)}" define_method(instruction_name) do |*args, &block| - instruction = WAG::Instruction.get_const(camelised_name).new(*args) + instruction = WAG::Instruction.const_get(camelised_name).new(*args) @instructions << instruction instruction.instance_exec(&block) if block instruction diff --git a/lib/wag/i64_instructions.rb b/lib/wag/i64_instructions.rb index ef5772e..f53260d 100644 --- a/lib/wag/i64_instructions.rb +++ b/lib/wag/i64_instructions.rb @@ -9,11 +9,11 @@ module WAG %i[load load8_s load8_u load16_s load16_u load32_s load32_u store store8 store16 store32 const eqz eq ne lt_s lt_u gt_s gt_u le_s le_u ge_s ge_u clz ctz popcnt add sub mul div_s div_u rem_s rem_u and or xor shl shr_s - shr_u rotl rotr extend_i13_s extend_i32_u trunc_f32_s trunc_f32_u + shr_u rotl rotr extend_i32_s extend_i32_u trunc_f32_s trunc_f32_u trunc_f64_s trunc_f64_u reinterpret_f64].each do |instruction_name| - camelised_name = "I32#{WAG::Inflector.inflector.camelize(instruction_name)}" + camelised_name = "I64::#{WAG::Inflector.inflector.camelize(instruction_name)}" define_method(instruction_name) do |*args, &block| - instruction = WAG::Instruction.get_const(camelised_name).new(*args) + instruction = WAG::Instruction.const_get(camelised_name).new(*args) @instructions << instruction instruction.instance_exec(&block) if block instruction diff --git a/lib/wag/import.rb b/lib/wag/import.rb index 81fbd4f..6c1ecba 100644 --- a/lib/wag/import.rb +++ b/lib/wag/import.rb @@ -23,14 +23,14 @@ module WAG @desc end - def memory(number, min: nil, max: nil, &block) - @desc = Memory.new(number, min: min, max: max) + def memory(number, min = nil, max = nil, &block) + @desc = Memory.new(number, min, max) @desc.instance_exec(&block) if block @desc end - def table(number, type = :funcref, min: nil, max: nil, &block) - @desc = Table.new(number, type, min: min, max: max) + def table(number, type = :funcref, &block) + @desc = Table.new(number, type) @desc.instance_exec(&block) if block @desc end diff --git a/lib/wag/instructable.rb b/lib/wag/instructable.rb index 49ede9f..58bd6c6 100644 --- a/lib/wag/instructable.rb +++ b/lib/wag/instructable.rb @@ -7,13 +7,21 @@ module WAG call_indirect drop select].each do |instruction_name| camelised_name = WAG::Inflector.inflector.camelize(instruction_name) define_method(instruction_name) do |*args, &block| - instruction = WAG::Instruction.get_const(camelised_name).new(*args) + instruction = WAG::Instruction.const_get(camelised_name).new(*args) instructions << instruction instruction.instance_exec(&block) if block instruction end end + def f32 + WAG::F32Instructions.new(instructions) + end + + def f64 + WAG::F64Instructions.new(instructions) + end + def local WAG::LocalInstructions.new(instructions) end diff --git a/lib/wag/instruction.rb b/lib/wag/instruction.rb index 6eb0bc0..420c5b8 100644 --- a/lib/wag/instruction.rb +++ b/lib/wag/instruction.rb @@ -133,6 +133,7 @@ module WAG require 'wag/instructions/i64/base' require 'wag/instructions/i64/add' + require 'wag/instructions/i64/and' require 'wag/instructions/i64/clz' require 'wag/instructions/i64/const' require 'wag/instructions/i64/ctz' diff --git a/lib/wag/instructions/base.rb b/lib/wag/instructions/base.rb index e4fc342..19ed6bc 100644 --- a/lib/wag/instructions/base.rb +++ b/lib/wag/instructions/base.rb @@ -7,7 +7,7 @@ module WAG prepend WAG::Instructable def self.instruction(op_code) - klass = Class.new(WAG::Instruction::Base) + klass = Class.new(self) klass.define_method(:op_code) { op_code } klass end diff --git a/lib/wag/instructions/i64/and.rb b/lib/wag/instructions/i64/and.rb new file mode 100644 index 0000000..49ef312 --- /dev/null +++ b/lib/wag/instructions/i64/and.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module WAG + module Instruction + module I64 + class And < Base.instruction(0x7c) + end + end + end +end diff --git a/lib/wag/local_instructions.rb b/lib/wag/local_instructions.rb index d53e18b..652a15b 100644 --- a/lib/wag/local_instructions.rb +++ b/lib/wag/local_instructions.rb @@ -7,9 +7,9 @@ module WAG end %i[get set tee].each do |instruction_name| - camelised_name = "Local#{WAG::Inflector.inflector.camelize(instruction_name)}" + camelised_name = "Local::#{WAG::Inflector.inflector.camelize(instruction_name)}" define_method(instruction_name) do |*args, &block| - instruction = WAG::Instruction.get_const(camelised_name).new(*args) + instruction = WAG::Instruction.const_get(camelised_name).new(*args) @instructions << instruction instruction.instance_exec(&block) if block instruction diff --git a/lib/wag/memory.rb b/lib/wag/memory.rb index c545d02..fac32f8 100644 --- a/lib/wag/memory.rb +++ b/lib/wag/memory.rb @@ -6,19 +6,19 @@ module WAG attr_reader :number, :min, :max - def initialize(number, min: nil, max: nil) + def initialize(number, min = nil, max = nil) @number = number @min = min @max = max end def to_sexpr - wat = [:memory, number] - - wat.concat([:min, min]) if min - wat.concat([:max, max]) if max - - wat + [:memory, number].tap do |expr| + if min + expr.push(min) + expr.push(max) if max + end + end end end end diff --git a/lib/wag/memory_instructions.rb b/lib/wag/memory_instructions.rb index df5bc11..af52a4f 100644 --- a/lib/wag/memory_instructions.rb +++ b/lib/wag/memory_instructions.rb @@ -7,9 +7,9 @@ module WAG end %i[size grow].each do |instruction_name| - camelised_name = "Memory#{WAG::Inflector.inflector.camelize(instruction_name)}" + camelised_name = "Memory::#{WAG::Inflector.inflector.camelize(instruction_name)}" define_method(instruction_name) do |*args, &block| - instruction = WAG::Instruction.get_const(camelised_name).new(*args) + instruction = WAG::Instruction.const_get(camelised_name).new(*args) @instructions << instruction instruction.instance_exec(&block) if block instruction diff --git a/lib/wag/module.rb b/lib/wag/module.rb index c36c21b..1f04758 100644 --- a/lib/wag/module.rb +++ b/lib/wag/module.rb @@ -29,8 +29,8 @@ module WAG export end - def memory(number, min: nil, max: nil, &block) - mem = Memory.new(number, min: min, max: max) + def memory(number, min = nil, max = nil, &block) + mem = Memory.new(number, min, max) @memories << mem mem.instance_exec(&block) if block mem @@ -63,7 +63,7 @@ module WAG data end - def func(label, &block) + def func(label = nil, &block) func = Function.new(label) @functions << func func.instance_exec(&block) if block diff --git a/lib/wag/table.rb b/lib/wag/table.rb index ac1e1c9..b39c287 100644 --- a/lib/wag/table.rb +++ b/lib/wag/table.rb @@ -3,27 +3,15 @@ module WAG class Table include WAG::WAT + attr_reader :elements, :type - RANGE = (0..0xFFFFFFFF).freeze - - attr_reader :number, :type, :min, :max - - def initialize(number, type = :funcref, min: nil, max: nil) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity - raise '`min` must fit in i32 range' if min && !RANGE.cover?(min) - raise '`max` must fit in i32 range' if max && !RANGE.cover?(max) - raise '`max` must be greater than `min`' if min && max && min > max - - @number = number + def initialize(elements, type = :funcref) + @elements = elements @type = type - @min = min - @max = max end def to_sexpr - expr = [:table, number, type] - expr.concat([:min, min]) if min - expr.concat([:max, max]) if max - expr + [:table, elements, type] end end end diff --git a/lib/wag/type.rb b/lib/wag/type.rb index b1f2015..ee5059c 100644 --- a/lib/wag/type.rb +++ b/lib/wag/type.rb @@ -3,10 +3,10 @@ module WAG module Type require 'wag/types/base' - require 'wag/types/i32' - require 'wag/types/i64' require 'wag/types/f32' require 'wag/types/f64' + require 'wag/types/i32' + require 'wag/types/i64' module_function diff --git a/lib/wag/types/function.rb b/lib/wag/types/function.rb deleted file mode 100644 index ee62d37..0000000 --- a/lib/wag/types/function.rb +++ /dev/null @@ -1,28 +0,0 @@ -# frozen_string_literal: true - -module WAG - module Type - # A WebAssembly function type. - class Function < Base - attr_reader :parameter_types, :return_types - - def initialize(parameter_types, return_types) - parameter_types.map! { |t| Huia::Type.from(t) } - return_types.map! { |t| Huia::Type.from(t) } - - raise 'There must only be one return type (at the moment)' unless return_types.one? - - unless parameter_types.all?(&:value?) && return_types.all?(&:value?) - raise 'Function type parameters can only be value types' - end - - @parameter_types = parameter_types - @return_types = return_types - end - - def value? - false - end - end - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index c88bf25..95b0578 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,6 +3,7 @@ require 'bundler/setup' require 'wag' require 'pry' +require 'faker' RSpec.configure do |config| # Enable flags like --only-failures and --next-failure diff --git a/spec/wag/export_spec.rb b/spec/wag/export_spec.rb index 1d50ddb..7cc4728 100644 --- a/spec/wag/export_spec.rb +++ b/spec/wag/export_spec.rb @@ -8,23 +8,23 @@ RSpec.describe WAG::Export do describe 'When exporting memory' do before do - export.memory(1, min: 1, max: 64) + export.memory(1, 1, 64) end describe '#to_sexpr' do subject { super().to_sexpr } - it { is_expected.to eq [:export, 'marty', [:memory, 1, :min, 1, :max, 64]] } + it { is_expected.to eq [:export, 'marty', [:memory, 1, 1, 64]] } end end describe 'When exporting a table' do before do - export.table(1, :funcref, min: 1, max: 64) + export.table(1, :funcref) end describe '#to_sexpr' do subject { super().to_sexpr } - it { is_expected.to eq [:export, 'marty', [:table, 1, :funcref, :min, 1, :max, 64]] } + it { is_expected.to eq [:export, 'marty', [:table, 1, :funcref]] } end end diff --git a/spec/wag/function_spec.rb b/spec/wag/function_spec.rb new file mode 100644 index 0000000..1308022 --- /dev/null +++ b/spec/wag/function_spec.rb @@ -0,0 +1,80 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::Function do + let(:label) { Faker::Name.first_name } + let(:function) { described_class.new(label) } + subject { function } + + describe 'When the function is given a label' do + let(:label) { Faker::Name.first_name } + + it 'correctly sets the label' do + expect(subject.label.value).to eq label + end + end + + describe '#param' do + let(:type) { :i32 } + subject { super().param(label, type) } + + it 'adds the param to the function' do + expect { subject } + .to change { function.params.size } + .from(0) + .to(1) + end + + it 'correctly sets the param' do + subject + expect(function.params.first).to be_a(WAG::Param) + expect(function.params.first.label.value).to eq(label) + expect(function.params.first.type).to be_a(WAG::Type::I32) + end + end + + describe '#result' do + subject { super().result(:i32) } + + it 'correctly sets the result' do + subject + expect(function.result).to be_a(WAG::Result) + expect(function.result.types.size).to eq(1) + expect(function.result.types.first).to be_a(WAG::Type::I32) + end + end + + describe '#to_sexpr' do + subject { super().to_sexpr } + + context 'When the function is empty' do + let(:label) { nil } + it { is_expected.to eq [:func] } + end + + context 'When the function has a label' do + it { is_expected.to eq [:func, "$#{label}".to_sym] } + + context 'And the function takes a parameter' do + before { function.param(:p0, :i32) } + it { is_expected.to eq [:func, "$#{label}".to_sym, %i[param $p0 i32]] } + + context 'And the function takes a second parameter' do + before { function.param(:p1, :f32) } + it { is_expected.to eq [:func, "$#{label}".to_sym, %i[param $p0 i32], %i[param $p1 f32]] } + end + end + + context 'When the function has a return type' do + before { function.result(:i64) } + it { is_expected.to eq [:func, "$#{label}".to_sym, %i[result i64]] } + end + + context 'When the function has a body' do + before { function.i64.const(13) } + it { is_expected.to eq [:func, "$#{label}".to_sym, [:"i64.const", 13]] } + end + end + end +end diff --git a/spec/wag/global_instructions_spec.rb b/spec/wag/global_instructions_spec.rb new file mode 100644 index 0000000..7a9aac8 --- /dev/null +++ b/spec/wag/global_instructions_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::GlobalInstructions do + let(:label) { Faker::Name.first_name } + let(:instructions) { [] } + subject { described_class.new(instructions) } + + %i[get set].each do |instruction_name| + describe "##{instruction_name}" do + subject { super().public_send(instruction_name, label) } + + it "adds a `global.#{instruction_name}` to the instructions" do + expect { subject } + .to change { instructions.size } + .from(0) + .to(1) + + expect(instructions.first) + .to be_a(WAG::Instruction::Global.const_get(WAG::Inflector.inflector.camelize(instruction_name))) + end + + it 'sets the label correctly' do + subject + expect(instructions.first.label.value) + .to eq(label) + end + end + end +end diff --git a/spec/wag/i32_instructions_spec.rb b/spec/wag/i32_instructions_spec.rb new file mode 100644 index 0000000..f213bf9 --- /dev/null +++ b/spec/wag/i32_instructions_spec.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::I32Instructions do + let(:instructions) { [] } + subject { described_class.new(instructions) } + + %i[load load8_s load8_u load16_s load16_u store store8 store16 eqz eq + ne lt_s lt_u gt_s gt_u le_s le_u ge_s ge_u clz ctz popcnt add sub mul + div_s div_u rem_s rem_u and or xor shl shr_s shr_u rotl rotr trunc_f32_s + trunc_f32_u trunc_f64_s trunc_f64_u reinterpret_f32].each do |instruction_name| + describe "##{instruction_name}" do + subject { super().public_send(instruction_name) } + + it "adds a `i32.#{instruction_name}` to the instructions" do + expect { subject } + .to change { instructions.size } + .from(0) + .to(1) + + expect(instructions.first) + .to be_a(WAG::Instruction::I32.const_get(WAG::Inflector.inflector.camelize(instruction_name))) + end + end + end + + describe '#const' do + let(:value) { rand(0xFFFFFFFF) } + subject { super().const(value) } + + it 'adds a `i32.const` to the instructions' do + expect { subject } + .to change { instructions.size } + .from(0) + .to(1) + + expect(instructions.first) + .to be_a(WAG::Instruction::I32::Const) + end + + it 'has the correct value' do + subject + expect(instructions.first.literal).to eq(value) + end + end +end diff --git a/spec/wag/i64_instructions_spec.rb b/spec/wag/i64_instructions_spec.rb new file mode 100644 index 0000000..9085c80 --- /dev/null +++ b/spec/wag/i64_instructions_spec.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::I64Instructions do + let(:instructions) { [] } + subject { described_class.new(instructions) } + + %i[load load8_s load8_u load16_s load16_u load32_s load32_u store store8 + store16 store32 eqz eq ne lt_s lt_u gt_s gt_u le_s le_u ge_s ge_u + clz ctz popcnt add sub mul div_s div_u rem_s rem_u and or xor shl shr_s + shr_u rotl rotr extend_i32_s extend_i32_u trunc_f32_s trunc_f32_u + trunc_f64_s trunc_f64_u reinterpret_f64].each do |instruction_name| + describe "##{instruction_name}" do + subject { super().public_send(instruction_name) } + + it "adds a `i64.#{instruction_name}` to the instructions" do + expect { subject } + .to change { instructions.size } + .from(0) + .to(1) + + expect(instructions.first) + .to be_a(WAG::Instruction::I64.const_get(WAG::Inflector.inflector.camelize(instruction_name))) + end + end + end + + describe '#const' do + let(:value) { rand(0xFFFFFFFFFFFFFFFF) } + subject { super().const(value) } + + it 'adds a `i64.const` to the instructions' do + expect { subject } + .to change { instructions.size } + .from(0) + .to(1) + + expect(instructions.first) + .to be_a(WAG::Instruction::I64::Const) + end + + it 'has the correct value' do + subject + expect(instructions.first.literal).to eq(value) + end + end +end diff --git a/spec/wag/import_spec.rb b/spec/wag/import_spec.rb index ab8bfef..8508015 100644 --- a/spec/wag/import_spec.rb +++ b/spec/wag/import_spec.rb @@ -8,23 +8,23 @@ RSpec.describe WAG::Import do describe 'When importing memory' do before do - import.memory(1, min: 1, max: 64) + import.memory(1, 1, 64) end describe '#to_sexpr' do subject { super().to_sexpr } - it { is_expected.to eq [:import, 'marty', 'mcfly', [:memory, 1, :min, 1, :max, 64]] } + it { is_expected.to eq [:import, 'marty', 'mcfly', [:memory, 1, 1, 64]] } end end describe 'When importing a table' do before do - import.table(1, :funcref, min: 1, max: 64) + import.table(1, :funcref) end describe '#to_sexpr' do subject { super().to_sexpr } - it { is_expected.to eq [:import, 'marty', 'mcfly', [:table, 1, :funcref, :min, 1, :max, 64]] } + it { is_expected.to eq [:import, 'marty', 'mcfly', [:table, 1, :funcref]] } end end diff --git a/spec/wag/inflector_spec.rb b/spec/wag/inflector_spec.rb new file mode 100644 index 0000000..d842764 --- /dev/null +++ b/spec/wag/inflector_spec.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::Inflector do + describe '.inflector' do + subject { described_class.inflector } + it { is_expected.to be_a(Dry::Inflector) } + end +end diff --git a/spec/wag/instructable_spec.rb b/spec/wag/instructable_spec.rb new file mode 100644 index 0000000..5e419f3 --- /dev/null +++ b/spec/wag/instructable_spec.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::Instructable do + subject do + Class.new do + include WAG::Instructable + end.new + end + + describe '.local' do + subject { super().local } + it { is_expected.to be_a(WAG::LocalInstructions) } + end + + describe '#f32' do + subject { super().f32 } + it { is_expected.to be_a(WAG::F32Instructions) } + end + + describe '#f64' do + subject { super().f64 } + it { is_expected.to be_a(WAG::F64Instructions) } + end + + describe '#i32' do + subject { super().i32 } + it { is_expected.to be_a(WAG::I32Instructions) } + end + + describe '#i64' do + subject { super().i64 } + it { is_expected.to be_a(WAG::I64Instructions) } + end + + describe '#memory' do + subject { super().memory } + it { is_expected.to be_a(WAG::MemoryInstructions) } + end + + %i[unreachable nop block loop if else end br_table return call + call_indirect drop select].each do |instruction_name| + describe "##{instruction_name}" do + subject { super().public_send(instruction_name) } + + it { is_expected.to be_a(WAG::Instruction.const_get(WAG::Inflector.inflector.camelize(instruction_name))) } + end + end + + %i[br br_if].each do |instruction_name| + describe "##{instruction_name}" do + let(:label) { Faker::Name.first_name } + subject { super().public_send(instruction_name, label) } + + it { is_expected.to be_a(WAG::Instruction.const_get(WAG::Inflector.inflector.camelize(instruction_name))) } + end + end +end diff --git a/spec/wag/label_spec.rb b/spec/wag/label_spec.rb new file mode 100644 index 0000000..6f02d98 --- /dev/null +++ b/spec/wag/label_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::Label do + let(:value) { Faker::Name.first_name } + subject { described_class.new(value) } + + describe '#to_sexpr' do + subject { super().to_sexpr } + it { is_expected.to eq "$#{value}".to_sym } + end + + describe '.from' do + subject { described_class.from(value) } + + it { is_expected.to be_a(described_class) } + + context 'When the value is a string' do + describe '#value' do + subject { super().value } + it { is_expected.to eq value } + end + end + + context 'When the value is already a label' do + let(:value) { described_class.new(Faker::Name.first_name) } + it { is_expected.to eq value } + end + end +end diff --git a/spec/wag/local_instructions_spec.rb b/spec/wag/local_instructions_spec.rb new file mode 100644 index 0000000..82cacbf --- /dev/null +++ b/spec/wag/local_instructions_spec.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::LocalInstructions do + let(:label) { Faker::Name.first_name } + let(:instructions) { [] } + subject { described_class.new(instructions) } + + %i[get set tee].each do |instruction_name| + describe "##{instruction_name}" do + subject { super().public_send(instruction_name, label) } + + it "adds a `local.#{instruction_name}` to the instructions" do + expect { subject } + .to change { instructions.size } + .from(0) + .to(1) + + expect(instructions.first) + .to be_a(WAG::Instruction::Local.const_get(WAG::Inflector.inflector.camelize(instruction_name))) + end + + it 'sets the label correctly' do + subject + expect(instructions.first.label.value) + .to eq(label) + end + end + end +end diff --git a/spec/wag/memory_instructions_spec.rb b/spec/wag/memory_instructions_spec.rb new file mode 100644 index 0000000..34b1147 --- /dev/null +++ b/spec/wag/memory_instructions_spec.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::MemoryInstructions do + let(:instructions) { [] } + subject { described_class.new(instructions) } + + describe '#size' do + subject { super().size } + + it 'adds a `memory.size` to the instructions' do + expect { subject } + .to change { instructions.size } + .from(0) + .to(1) + + expect(instructions.first) + .to be_a(WAG::Instruction::Memory::Size) + end + end + + describe '#grow' do + subject { super().grow } + + it 'adds a `memory.grow` to the instructions' do + expect { subject } + .to change { instructions.size } + .from(0) + .to(1) + + expect(instructions.first) + .to be_a(WAG::Instruction::Memory::Grow) + end + end +end diff --git a/spec/wag/memory_spec.rb b/spec/wag/memory_spec.rb new file mode 100644 index 0000000..0f6434b --- /dev/null +++ b/spec/wag/memory_spec.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::Memory do + let(:number) { rand(99) } + let(:min) { nil } + let(:max) { nil } + subject { described_class.new(number, min, max) } + + describe '#to_sexpr' do + subject { super().to_sexpr } + + describe 'When there is no minumum value' do + it { is_expected.to eq [:memory, number] } + end + + describe 'When there is a minumum value' do + let(:min) { rand(99) } + it { is_expected.to eq [:memory, number, min] } + + describe 'When there is a max value' do + let(:max) { min + rand(99) } + it { is_expected.to eq [:memory, number, min, max] } + end + end + end +end diff --git a/spec/wag/param_spec.rb b/spec/wag/param_spec.rb new file mode 100644 index 0000000..7e73023 --- /dev/null +++ b/spec/wag/param_spec.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::Param do + let(:type) { :i32 } + subject { described_class.new(type, label) } + + describe '#to_sexpr' do + subject { super().to_sexpr } + + context 'When the param has a label' do + let(:label) { :foo } + it { is_expected.to eq %i[param $foo i32] } + end + + context 'When the param has no label' do + let(:label) { nil } + it { is_expected.to eq %i[param i32] } + end + end +end diff --git a/spec/wag/result_spec.rb b/spec/wag/result_spec.rb new file mode 100644 index 0000000..bd7a732 --- /dev/null +++ b/spec/wag/result_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::Result do + let(:types) { %i[i32 i64] } + subject { described_class.new(*types) } + + describe '#to_sexpr' do + subject { super().to_sexpr } + it { is_expected.to eq %i[result i32 i64] } + end +end diff --git a/spec/wag/table_spec.rb b/spec/wag/table_spec.rb new file mode 100644 index 0000000..4c2eb49 --- /dev/null +++ b/spec/wag/table_spec.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::Table do + let(:elements) { rand(99) } + subject { described_class.new(elements) } + + describe '#to_sexpr' do + subject { super().to_sexpr } + it { is_expected.to eq [:table, elements, :funcref] } + end +end diff --git a/spec/wag/type_spec.rb b/spec/wag/type_spec.rb new file mode 100644 index 0000000..e42ee77 --- /dev/null +++ b/spec/wag/type_spec.rb @@ -0,0 +1,63 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::Type do + describe '.from' do + subject { described_class.from(argument) } + + context 'When the argument is `:f32`' do + let(:argument) { :f32 } + it { is_expected.to be_a(WAG::Type::F32) } + end + + context 'When the argument is `:f64`' do + let(:argument) { :f64 } + it { is_expected.to be_a(WAG::Type::F64) } + end + + context 'When the argument is `:i32`' do + let(:argument) { :i32 } + it { is_expected.to be_a(WAG::Type::I32) } + end + + context 'When the argument is `:i64`' do + let(:argument) { :i64 } + it { is_expected.to be_a(WAG::Type::I64) } + end + + context 'When the argument is a `WAG::Type::F32`' do + let(:argument) { WAG::Type::F32.new } + it { is_expected.to eq argument } + end + + context 'When the argument is a `WAG::Type::F64`' do + let(:argument) { WAG::Type::F64.new } + it { is_expected.to eq argument } + end + + context 'When the argument is a `WAG::Type::I32`' do + let(:argument) { WAG::Type::I32.new } + it { is_expected.to eq argument } + end + + context 'When the argument is a `WAG::Type::I64`' do + let(:argument) { WAG::Type::I64.new } + it { is_expected.to eq argument } + end + + context 'When the argument is an unknown symbol' do + let(:argument) { :marty_mcfly } + it 'raises a NameError' do + expect { subject }.to raise_error(NameError) + end + end + + context 'When the argument is an unknown object' do + let(:argument) { WAG::Instruction::Nop.new } + it 'raises a NameError' do + expect { subject }.to raise_error(NameError) + end + end + end +end diff --git a/spec/wag/types/f32_spec.rb b/spec/wag/types/f32_spec.rb new file mode 100644 index 0000000..ebfb5b3 --- /dev/null +++ b/spec/wag/types/f32_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::Type::F32 do + it { is_expected.to be_a(WAG::Type::Base) } + + describe '#to_sexpr' do + subject { super().to_sexpr } + it { is_expected.to eq :f32 } + end +end diff --git a/spec/wag/types/f64_spec.rb b/spec/wag/types/f64_spec.rb new file mode 100644 index 0000000..5f848ac --- /dev/null +++ b/spec/wag/types/f64_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::Type::F64 do + it { is_expected.to be_a(WAG::Type::Base) } + + describe '#to_sexpr' do + subject { super().to_sexpr } + it { is_expected.to eq :f64 } + end +end diff --git a/spec/wag/types/i32_spec.rb b/spec/wag/types/i32_spec.rb new file mode 100644 index 0000000..de1cbee --- /dev/null +++ b/spec/wag/types/i32_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::Type::I32 do + it { is_expected.to be_a(WAG::Type::Base) } + + describe '#to_sexpr' do + subject { super().to_sexpr } + it { is_expected.to eq :i32 } + end +end diff --git a/spec/wag/types/i64_spec.rb b/spec/wag/types/i64_spec.rb new file mode 100644 index 0000000..dc31404 --- /dev/null +++ b/spec/wag/types/i64_spec.rb @@ -0,0 +1,12 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe WAG::Type::I64 do + it { is_expected.to be_a(WAG::Type::Base) } + + describe '#to_sexpr' do + subject { super().to_sexpr } + it { is_expected.to eq :i64 } + end +end