Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add parser round trip test to CI #298

Merged
merged 1 commit into from
Feb 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,22 @@ jobs:
bundler-cache: true
ruby-version: ${{ matrix.ruby }}
- run: bundle exec bin/corpus
ruby-parser-round-trip-tests:
name: Parser Round Trip Tests
runs-on: ${{ matrix.os }}
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
ruby: [ruby-2.6, ruby-2.7, ruby-3.0]
os: [macos-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v2
- uses: ruby/setup-ruby@v1
with:
bundler-cache: true
ruby-version: ${{ matrix.ruby }}
- run: bundle exec ruby bin/parser-round-trip-test
ruby-rubocop:
name: Rubocop
runs-on: ${{ matrix.os }}
Expand Down
4 changes: 4 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

* Fix emit of of `match_pattern` vs `match_pattern_p`

[#298](https://github.com/mbj/unparser/pull/298)

* Add round trip tests dynamically derived from the `parser` gems test suite to CI

# v0.6.3 2022-01-16

[#290](https://github.com/mbj/unparser/pull/290)
Expand Down
138 changes: 93 additions & 45 deletions test/run-parser-tests.rb → bin/parser-round-trip-test
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,19 +1,33 @@
#!/usr/bin/env ruby
# frozen_string_literal: true

require 'unparser'

$: << __dir__
# Hack to dynamically re-use the `parser` gems test suite on CI.
# The main idea is create a fake minitet runner to capture the
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minitet typo, fyi

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thx. Fixing on next touch.

# signature of the examples encoded in the parsers test suite dynamically.
#
# This makes maintenance much more easier, especially on tracking new ruby
# syntax addtions.
#
# The API surface of the parser tests so far is low churn, while it may still
# make sense to provide the parser tests as an more easy to re-use data upstream.

$LOAD_PATH << Pathname.new(__dir__).parent.join('test')

testBuilder = Class.new(Parser::Builders::Default)
testBuilder.modernize
test_builder = Class.new(Parser::Builders::Default)
test_builder.modernize

MODERN_ATTRIBUTES = testBuilder.instance_variables.map do |instance_variable|
attribute_name = instance_variable.to_s[1..-1].to_sym
[attribute_name, testBuilder.public_send(attribute_name)]
end.to_h
MODERN_ATTRIBUTES = test_builder.instance_variables.to_h do |instance_variable|
attribute_name = instance_variable.to_s[1..].to_sym
[attribute_name, test_builder.public_send(attribute_name)]
end

# Overwrite global scope method in the parser test suite
def default_builder_attributes
MODERN_ATTRIBUTES.keys.map do |attribute_name|
MODERN_ATTRIBUTES.keys.to_h do |attribute_name|
[attribute_name, Parser::Builders::Default.public_send(attribute_name)]
end.to_h
end
end

class Test
Expand All @@ -26,13 +40,11 @@ class Test
:rubies
)

TARGET_RUBIES = %w[2.6 2.7]

EXPECT_FAILURE = {}.freeze

def legacy_attributes
default_builder_attributes.select do |attribute_name, value|
!MODERN_ATTRIBUTES.fetch(attribute_name).equal?(value)
default_builder_attributes.reject do |attribute_name, value|
MODERN_ATTRIBUTES.fetch(attribute_name).equal?(value)
end.to_h
end
memoize :legacy_attributes
Expand All @@ -43,7 +55,7 @@ def skip_reason
elsif !allow_ruby?
"Non targeted rubies: #{rubies.join(',')}"
elsif validation.original_node.left?
"Test specifies a syntax error"
'Test specifies a syntax error'
end
end

Expand All @@ -56,18 +68,21 @@ def expect_failure?
end

def allow_ruby?
rubies.empty? || rubies.any?(TARGET_RUBIES.method(:include?))
rubies.empty? || rubies.include?(RUBY_VERSION)
end

def right(value)
Unparser::Either::Right.new(value)
end

# rubocop:disable Metrics/AbcSize
def validation
identification = name.to_s

generated_source = Unparser.unparse_either(node)
.fmap { |string| string.dup.force_encoding(parser_source.encoding).freeze }

generated_node = generated_source.lmap{ |_value| }.bind do |source|
generated_node = generated_source.bind do |source|
parse_either(source, identification)
end

Expand All @@ -79,11 +94,12 @@ def validation
original_source: right(parser_source)
)
end
# rubocop:enable Metrics/AbcSize
memoize :validation

def parser
Unparser.parser.tap do |parser|
%w(foo bar baz).each(&parser.static_env.method(:declare))
%w[foo bar baz].each(&parser.static_env.method(:declare))
end
end

Expand Down Expand Up @@ -115,7 +131,7 @@ def call

def expect_failure
if test.success?
fail format('Expected Failure', 'but got success')
message('Expected Failure', 'but got success')
else
print('Expected Failure')
end
Expand All @@ -126,22 +142,32 @@ def expect_success
print('Success')
else
puts(test.validation.report)
fail format('Failure')
fail message('Failure')
end
end

def format(status, message = '')
'%3d/%3d: %-16s %s[%02d] %s' % [number, total, status, test.name, test.group_index, message]
def message(status, message = '')
format(
'%3<number>d/%3<total>d: %-16<status>s %<name>s[%02<group_index>d] %<message>s',
number: number,
total: total,
status: status,
name: test.name,
group_index: test.group_index,
message: message
)
end

def print(status, message = '')
puts(format(status, message))
puts(message(status, message))
end
end

module Minitest
class Test
end # Test
# Stub parent class
# rubocop:disable Lint/EmptyClass
class Test; end # Test
# rubocop:enable Lint/EmptyClass
end # Minitest

class Extractor
Expand Down Expand Up @@ -183,46 +209,68 @@ def call(name)
end
end

require '../parser/test/parse_helper.rb'
require '../parser/test/test_parser.rb'
PARSER_PATH = Pathname.new('tmp/parser')

unless PARSER_PATH.exist?
Kernel.system(
*%W[
git
clone
https://github.com/whitequark/parser
#{PARSER_PATH}
],
exception: true
)
end

Dir.chdir(PARSER_PATH) do
Kernel.system(
*%W[
git
checkout
v#{Parser::VERSION}
],
exception: true
)
Kernel.system(*%w[git clean --force -d -X], exception: true)
end

require "./#{PARSER_PATH}/test/parse_helper"
require "./#{PARSER_PATH}/test/test_parser"

EXTRACTOR = Extractor.new

module ParseHelper
def assert_diagnoses(*arguments)
end
def assert_diagnoses(*arguments); end

def s(type, *children)
Parser::AST::Node.new(type, children)
end

# rubocop:disable Metrics/ParameterLists
def assert_parses(node, parser_source, _diagnostics = nil, rubies = [])
EXTRACTOR.capture(
default_builder_attributes: default_builder_attributes,
node: node,
parser_source: parser_source,
rubies: rubies
)
EXTRACTOR.capture(
default_builder_attributes: default_builder_attributes,
node: node,
parser_source: parser_source,
rubies: rubies
)
end
# rubocop:enable Metrics/ParameterLists

def test_clrf_line_endings(*arguments)
end
def test_clrf_line_endings(*arguments); end

def with_versions(*arguments)
end
def with_versions(*arguments); end

def assert_context(*arguments)
end
def assert_context(*arguments); end

def refute_diagnoses(*arguments)
end
def refute_diagnoses(*arguments); end

def assert_diagnoses_many(*arguments)
end
def assert_diagnoses_many(*arguments); end
end

TestParser.instance_methods.grep(/\Atest_/).each(&EXTRACTOR.method(:call))

EXTRACTOR.tests.sort_by(&:name).each_with_index do |test, index|
Execution.new(number: index.succ, total: EXTRACTOR.tests.length, test: test).call
Execution.new(number: index.succ, total: EXTRACTOR.tests.length, test: test).call
end