opal 1.7.0 → 1.7.2

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +12 -4
  3. data/CHANGELOG.md +39 -1
  4. data/UNRELEASED.md +1 -0
  5. data/docs/cdp_common.md +1 -1
  6. data/docs/compiler.md +1 -1
  7. data/docs/releasing.md +24 -8
  8. data/exe/opal +5 -7
  9. data/lib/opal/builder.rb +13 -13
  10. data/lib/opal/builder_processors.rb +8 -2
  11. data/lib/opal/builder_scheduler/prefork.rb +64 -9
  12. data/lib/opal/cli.rb +60 -43
  13. data/lib/opal/cli_runners/compiler.rb +7 -6
  14. data/lib/opal/cli_runners/safari.rb +208 -0
  15. data/lib/opal/cli_runners.rb +1 -0
  16. data/lib/opal/nodes/literal.rb +16 -0
  17. data/lib/opal/version.rb +1 -1
  18. data/opal/corelib/constants.rb +3 -3
  19. data/spec/filters/platform/.keep +0 -0
  20. data/spec/filters/platform/firefox/exception.rb +8 -0
  21. data/spec/filters/platform/firefox/kernel.rb +3 -0
  22. data/spec/filters/platform/safari/exception.rb +8 -0
  23. data/spec/filters/platform/safari/float.rb +4 -0
  24. data/spec/filters/platform/safari/kernel.rb +3 -0
  25. data/spec/filters/platform/safari/literal_regexp.rb +6 -0
  26. data/spec/lib/builder_spec.rb +32 -0
  27. data/spec/lib/cli_spec.rb +28 -6
  28. data/spec/lib/fixtures/build_order/file1.js +1 -0
  29. data/spec/lib/fixtures/build_order/file2.js +1 -0
  30. data/spec/lib/fixtures/build_order/file3.js +1 -0
  31. data/spec/lib/fixtures/build_order/file4.js +1 -0
  32. data/spec/lib/fixtures/build_order/file5.rb.erb +4 -0
  33. data/spec/lib/fixtures/build_order/file51.js +1 -0
  34. data/spec/lib/fixtures/build_order/file6.rb +10 -0
  35. data/spec/lib/fixtures/build_order/file61.rb +1 -0
  36. data/spec/lib/fixtures/build_order/file62.rb +4 -0
  37. data/spec/lib/fixtures/build_order/file63.rb +4 -0
  38. data/spec/lib/fixtures/build_order/file64.rb +1 -0
  39. data/spec/lib/fixtures/build_order/file7.rb +1 -0
  40. data/spec/lib/fixtures/build_order.rb +9 -0
  41. data/spec/lib/rake_dist_spec.rb +69 -0
  42. data/spec/lib/spec_helper.rb +2 -0
  43. data/spec/mspec-opal/runner.rb +1 -0
  44. data/spec/opal/core/io/read_spec.rb +12 -3
  45. data/stdlib/opal/platform.rb +1 -0
  46. data/stdlib/opal-platform.rb +3 -0
  47. data/stdlib/time.rb +21 -1
  48. data/tasks/building.rake +1 -1
  49. data/tasks/releasing.rake +46 -0
  50. data/tasks/testing.rake +3 -4
  51. metadata +30 -7
@@ -0,0 +1,208 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+ require 'socket'
5
+ require 'timeout'
6
+ require 'tmpdir'
7
+ require 'rbconfig'
8
+ require 'opal/os'
9
+ require 'net/http'
10
+ require 'webrick'
11
+
12
+ module Opal
13
+ module CliRunners
14
+ class Safari
15
+ EXECUTION_TIMEOUT = 600 # seconds
16
+ DEFAULT_SAFARI_DRIVER_HOST = 'localhost'
17
+ DEFAULT_SAFARI_DRIVER_PORT = 9444 # in addition safari_driver_port + 1 is used for the http server
18
+
19
+ def self.call(data)
20
+ runner = new(data)
21
+ runner.run
22
+ end
23
+
24
+ def initialize(data)
25
+ argv = data[:argv]
26
+ if argv && argv.any?
27
+ warn "warning: ARGV is not supported by the Safari runner #{argv.inspect}"
28
+ end
29
+
30
+ options = data[:options]
31
+ @output = options.fetch(:output, $stdout)
32
+ @builder = data[:builder].call
33
+ end
34
+
35
+ attr_reader :output, :exit_status, :builder
36
+
37
+ def run
38
+ mktmpdir do |dir|
39
+ with_http_server(dir) do |http_port, server_thread|
40
+ with_safari_driver do
41
+ prepare_files_in(dir)
42
+
43
+ # Safaridriver commands are very limitied, for supported commands see:
44
+ # https://developer.apple.com/documentation/webkit/macos_webdriver_commands_for_safari_12_and_later
45
+ Net::HTTP.start(safari_driver_host, safari_driver_port) do |con|
46
+ con.read_timeout = EXECUTION_TIMEOUT
47
+ res = con.post('/session', { capabilities: { browserName: 'Safari' } }.to_json, 'Content-Type' => 'application/json')
48
+ session_id = JSON.parse(res.body).dig('value', 'sessionId')
49
+ if session_id
50
+ session_path = "/session/#{session_id}"
51
+ con.post("#{session_path}/url", { url: "http://#{safari_driver_host}:#{http_port}/index.html" }.to_json, 'Content-Type' => 'application/json')
52
+ server_thread.join(EXECUTION_TIMEOUT)
53
+ else
54
+ STDERR.puts "Could not create session: #{res.body}"
55
+ end
56
+ end
57
+ 0
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def prepare_files_in(dir)
66
+ # The safaridriver is very limited in capabilities, basically it can trigger visiting sites
67
+ # and interact a bit with the page. So this runner starts its own server, overwrites the
68
+ # console log, warn, error functions of the browser and triggers a request after execution
69
+ # to exit. Certain exceptions cannot be caught that way and everything may fail in between,
70
+ # thats why execution is timed out after EXECUTION_TIMEOUT (10 minutes).
71
+ # As a side effect, console messages may arrive out of order and timing anything may be inaccurate.
72
+
73
+ builder.build_str <<~RUBY, '(exit)', no_export: true
74
+ %x{
75
+ var req = new XMLHttpRequest();
76
+ req.open("GET", '/exit');
77
+ req.send();
78
+ }
79
+ RUBY
80
+
81
+ js = builder.to_s
82
+ map = builder.source_map.to_json
83
+ ext = builder.output_extension
84
+ module_type = ' type="module"' if builder.esm?
85
+
86
+ File.binwrite("#{dir}/index.#{ext}", js)
87
+ File.binwrite("#{dir}/index.map", map)
88
+ File.binwrite("#{dir}/index.html", <<~HTML)
89
+ <html><head>
90
+ <meta charset='utf-8'>
91
+ <link rel="icon" href="data:;base64,=">
92
+ </head><body>
93
+ <script>
94
+ var orig_log = console.log;
95
+ var orig_err = console.error;
96
+ var orig_warn = console.warn;
97
+ function send_log_request(args) {
98
+ var req = new XMLHttpRequest();
99
+ req.open("POST", '/log');
100
+ req.setRequestHeader("Content-Type", "application/json");
101
+ req.send(JSON.stringify(args));
102
+ }
103
+ console.log = function() {
104
+ orig_log.apply(null, arguments);
105
+ send_log_request(arguments);
106
+ }
107
+ console.error = function() {
108
+ orig_err.apply(null, arguments);
109
+ send_log_request(arguments);
110
+ }
111
+ console.warn = function() {
112
+ orig_warn.apply(null, arguments);
113
+ send_log_request(arguments);
114
+ }
115
+
116
+ </script>
117
+ <script src='./index.#{ext}'#{module_type}></script>
118
+ </body></html>
119
+ HTML
120
+
121
+ # <script src='./index.#{ext}'#{module_type}></script>
122
+ end
123
+
124
+ def safari_driver_host
125
+ ENV['SAFARI_DRIVER_HOST'] || DEFAULT_SAFARI_DRIVER_HOST
126
+ end
127
+
128
+ def safari_driver_port
129
+ ENV['SAFARI_DRIVER_PORT'] || DEFAULT_SAFARI_DRIVER_PORT
130
+ end
131
+
132
+ def with_http_server(dir)
133
+ port = safari_driver_port.to_i + 1
134
+ server_thread = Thread.new do
135
+ server = WEBrick::HTTPServer.new(Port: port, DocumentRoot: dir, Logger: WEBrick::Log.new('/dev/null'), AccessLog: [])
136
+ server.mount_proc('/log') do |req, res|
137
+ if req.body
138
+ par = JSON.parse(req.body)
139
+ par.each_value do |value|
140
+ print value.to_s
141
+ end
142
+ end
143
+ res.header['Content-Type'] = 'text/plain'
144
+ res.body = ''
145
+ end
146
+ server.mount_proc('/exit') do
147
+ server_thread.kill
148
+ end
149
+ server.start
150
+ end
151
+
152
+ yield port, server_thread
153
+ rescue
154
+ exit(1)
155
+ ensure
156
+ server_thread.kill if server_thread
157
+ end
158
+
159
+ def with_safari_driver
160
+ if safari_driver_running?
161
+ yield
162
+ else
163
+ run_safari_driver { yield }
164
+ end
165
+ end
166
+
167
+ def run_safari_driver
168
+ raise 'Safari driver can be started only on localhost' if safari_driver_host != DEFAULT_SAFARI_DRIVER_HOST
169
+
170
+ started = false
171
+
172
+ safari_driver_cmd = %{/usr/bin/safaridriver \
173
+ -p #{safari_driver_port} \
174
+ #{ENV['SAFARI_DRIVER_OPTS']}}
175
+
176
+ safari_driver_pid = Process.spawn(safari_driver_cmd, in: OS.dev_null, out: OS.dev_null, err: OS.dev_null)
177
+
178
+ Timeout.timeout(30) do
179
+ loop do
180
+ break if safari_driver_running?
181
+ sleep 0.5
182
+ end
183
+ end
184
+
185
+ started = true
186
+
187
+ yield
188
+ rescue Timeout::Error => e
189
+ puts started ? 'Execution timed out' : 'Failed to start Safari driver'
190
+ raise e
191
+ ensure
192
+ Process.kill('HUP', safari_driver_pid) if safari_driver_pid
193
+ end
194
+
195
+ def safari_driver_running?
196
+ puts "Connecting to #{safari_driver_host}:#{safari_driver_port}..."
197
+ TCPSocket.new(safari_driver_host, safari_driver_port).close
198
+ true
199
+ rescue Errno::ECONNREFUSED, Errno::EADDRNOTAVAIL
200
+ false
201
+ end
202
+
203
+ def mktmpdir(&block)
204
+ Dir.mktmpdir('safari-opal-', &block)
205
+ end
206
+ end
207
+ end
208
+ end
@@ -93,6 +93,7 @@ module Opal
93
93
 
94
94
  if OS.macos?
95
95
  register_runner :applescript, :Applescript, 'opal/cli_runners/applescript'
96
+ register_runner :safari, :Safari, 'opal/cli_runners/safari'
96
97
  alias_runner :osascript, :applescript
97
98
  end
98
99
  end
@@ -158,11 +158,27 @@ module Opal
158
158
  case value
159
159
  when ''
160
160
  push('/(?:)/')
161
+ when /\(\?<=|\(\?<!/
162
+ # Safari/WebKit will not execute javascript code if it contains a lookbehind literal RegExp
163
+ # and they fail with "Syntax Error". This tricks their parser by disguising the literal RegExp
164
+ # as string for the dynamic $regexp helper. Safari/Webkit will still fail to execute the RegExp,
165
+ # but at least they will parse and run everything else.
166
+ static_as_dynamic(value)
161
167
  else
162
168
  push "#{Regexp.new(value).inspect}#{flags.join}"
163
169
  end
164
170
  end
165
171
 
172
+ def static_as_dynamic(value)
173
+ helper :regexp
174
+
175
+ push '$regexp(["'
176
+ push value.gsub('\\', '\\\\\\\\')
177
+ push '"]'
178
+ push ", '#{flags.join}'" if flags.any?
179
+ push ")"
180
+ end
181
+
166
182
  def extract_flags_and_value
167
183
  *values, flags_sexp = *children
168
184
  self.flags = flags_sexp.children.map(&:to_s)
data/lib/opal/version.rb CHANGED
@@ -3,5 +3,5 @@
3
3
  module Opal
4
4
  # WHEN RELEASING:
5
5
  # Remember to update RUBY_ENGINE_VERSION in opal/corelib/constants.rb too!
6
- VERSION = '1.7.0'
6
+ VERSION = '1.7.2'
7
7
  end
@@ -1,9 +1,9 @@
1
1
  ::RUBY_PLATFORM = 'opal'
2
2
  ::RUBY_ENGINE = 'opal'
3
3
  ::RUBY_VERSION = '3.2.0'
4
- ::RUBY_ENGINE_VERSION = '1.7.0'
5
- ::RUBY_RELEASE_DATE = '2022-12-26'
4
+ ::RUBY_ENGINE_VERSION = '1.7.2'
5
+ ::RUBY_RELEASE_DATE = '2023-01-20'
6
6
  ::RUBY_PATCHLEVEL = 0
7
7
  ::RUBY_REVISION = '0'
8
- ::RUBY_COPYRIGHT = 'opal - Copyright (C) 2013-2022 Adam Beynon and the Opal contributors'
8
+ ::RUBY_COPYRIGHT = 'opal - Copyright (C) 2011-2023 Adam Beynon and the Opal contributors'
9
9
  ::RUBY_DESCRIPTION = "opal #{::RUBY_ENGINE_VERSION} (#{::RUBY_RELEASE_DATE} revision #{::RUBY_REVISION})"
File without changes
@@ -0,0 +1,8 @@
1
+ # Source map support currently is only available for Chrome and Nodejs
2
+ opal_unsupported_filter "Exception" do
3
+ fails "Exception#backtrace_locations sets each element to a Thread::Backtrace::Location"
4
+ fails "Exception#backtrace contains lines of the same format for each prior position in the stack"
5
+ fails "Exception#backtrace sets each element to a String"
6
+ fails "Invoking a method when the method is not available should omit the method_missing call from the backtrace for NoMethodError"
7
+ fails "Invoking a method when the method is not available should omit the method_missing call from the backtrace for NameError"
8
+ end
@@ -0,0 +1,3 @@
1
+ opal_unsupported_filter "Kernel" do
2
+ fails "Kernel#caller includes core library methods defined in Ruby"
3
+ end
@@ -0,0 +1,8 @@
1
+ # Source map support currently is only available for Chrome and Nodejs
2
+ opal_unsupported_filter "Exception" do
3
+ fails "Exception#backtrace contains lines of the same format for each prior position in the stack"
4
+ fails "Exception#backtrace_locations sets each element to a Thread::Backtrace::Location"
5
+ fails "Exception#backtrace returns an Array that can be updated"
6
+ fails "Exception#backtrace returns the same array after duping"
7
+ fails "Exception#backtrace sets each element to a String"
8
+ end
@@ -0,0 +1,4 @@
1
+ opal_filter "Float" do
2
+ fails "Float#fdiv performs floating-point division between self and an Integer" # Expected 8.900000000008007e-117 == 8.900000000008011e-117 to be truthy but was false
3
+ fails "Float#quo performs floating-point division between self and an Integer" # Expected 8.900000000008007e-117 == 8.900000000008011e-117 to be truthy but was false
4
+ end
@@ -0,0 +1,3 @@
1
+ opal_unsupported_filter "Kernel" do
2
+ fails "Kernel#caller includes core library methods defined in Ruby"
3
+ end
@@ -0,0 +1,6 @@
1
+ opal_unsupported_filter "Literal Regexp" do
2
+ # Safari and WebKit do not support lookbehind, but may in the future see https://github.com/WebKit/WebKit/pull/7109
3
+ fails "Literal Regexps handles a lookbehind with ss characters"
4
+ fails "Literal Regexps supports (?<= ) (positive lookbehind)"
5
+ fails "Literal Regexps supports (?<! ) (negative lookbehind)"
6
+ end
@@ -154,4 +154,36 @@ RSpec.describe Opal::Builder do
154
154
  expect(builder.build('opal/platform', requirable: true).to_s).to include(%{Opal.modules["opal/platform"]})
155
155
  end
156
156
  end
157
+
158
+ describe 'output order' do
159
+ it 'is preserved with a prefork scheduler' do
160
+ my_builder = builder.dup
161
+ my_builder.append_paths(File.expand_path('..', __FILE__))
162
+ my_builder.cache = Opal::Cache::NullCache.new
163
+ 10.times do |i| # Increase entropy
164
+ expect(
165
+ my_builder.dup.build('fixtures/build_order').to_s.scan(/(FILE_[0-9]+)/).map(&:first)
166
+ ).to eq(%w[
167
+ FILE_1 FILE_2 FILE_3 FILE_4
168
+ FILE_51 FILE_5
169
+ FILE_61 FILE_62 FILE_63 FILE_64 FILE_6
170
+ FILE_7
171
+ ])
172
+ end
173
+ end
174
+
175
+ it 'is preserved with a sequential scheduler' do
176
+ temporarily_with_sequential_scheduler do
177
+ builder.append_paths(File.expand_path('..', __FILE__))
178
+ expect(
179
+ builder.build('fixtures/build_order').to_s.scan(/(FILE_[0-9]+)/).map(&:first)
180
+ ).to eq(%w[
181
+ FILE_1 FILE_2 FILE_3 FILE_4
182
+ FILE_51 FILE_5
183
+ FILE_61 FILE_62 FILE_63 FILE_64 FILE_6
184
+ FILE_7
185
+ ])
186
+ end
187
+ end
188
+ end
157
189
  end
data/spec/lib/cli_spec.rb CHANGED
@@ -9,7 +9,7 @@ RSpec.describe Opal::CLI do
9
9
  subject(:cli) { described_class.new(options) }
10
10
 
11
11
  context 'with a file' do
12
- let(:options) { {:file => File.open(file)} }
12
+ let(:options) { {argv: [file]} }
13
13
 
14
14
  it 'runs the file' do
15
15
  expect_output_of{ subject.run }.to eq("hi from opal!\n")
@@ -26,8 +26,10 @@ RSpec.describe Opal::CLI do
26
26
 
27
27
  describe ':evals option' do
28
28
  context 'without evals and paths' do
29
- it 'raises ArgumentError' do
30
- expect { subject.run }.to raise_error(ArgumentError)
29
+ it 'uses stdin' do
30
+ expect(cli.file).to eq($stdin)
31
+ expect(cli.filename).to eq('-')
32
+ expect(cli.argv).to eq([])
31
33
  end
32
34
 
33
35
  context 'with lib_only: true and opal require' do
@@ -40,7 +42,7 @@ RSpec.describe Opal::CLI do
40
42
  end
41
43
 
42
44
  context 'with one eval' do
43
- let(:options) { {:evals => ['puts "hello"']} }
45
+ let(:options) { {evals: ['puts "hello"']} }
44
46
 
45
47
  it 'executes the code' do
46
48
  expect_output_of{ subject.run }.to eq("hello\n")
@@ -56,12 +58,22 @@ RSpec.describe Opal::CLI do
56
58
  end
57
59
 
58
60
  context 'with many evals' do
59
- let(:options) { {:evals => ['puts "hello"', 'puts "ciao"']} }
61
+ let(:options) { {evals: ['puts "hello"', 'puts "ciao"']} }
60
62
 
61
63
  it 'executes the code' do
62
64
  expect_output_of{ subject.run }.to eq("hello\nciao\n")
63
65
  end
64
66
  end
67
+
68
+ context 'with a file path arg' do
69
+ let(:options) { {:evals => ['puts "hello"'], argv: [file]} }
70
+
71
+ it 'considers it as part of ARGV' do
72
+ expect(cli.argv).to eq([file])
73
+ expect(cli.file.tap(&:rewind).read).to eq('puts "hello"')
74
+ expect(cli.filename).to eq('-e')
75
+ end
76
+ end
65
77
  end
66
78
 
67
79
  describe ':no_exit option' do
@@ -229,13 +241,23 @@ RSpec.describe Opal::CLI do
229
241
 
230
242
  describe ':enable_source_location' do
231
243
  let(:file) { File.expand_path('../fixtures/source_location_test.rb', __FILE__) }
232
- let(:options) { { enable_source_location: true, runner: :compiler, file: File.open(file) } }
244
+ let(:options) { { enable_source_location: true, runner: :compiler, argv: [file] } }
233
245
 
234
246
  it 'sets $$source_location prop for compiled methods' do
235
247
  expect_output_of { subject.run }.to include("source_location_test.rb', 6]")
236
248
  end
237
249
  end
238
250
 
251
+ describe ':debug_source_map' do
252
+ let(:options) { { debug_source_map: true, evals: ['puts 123'] } }
253
+
254
+ it 'generates a link to https://sokra.github.io/source-map-visualization' do
255
+ expect_output_of { subject.run }.to start_with(
256
+ "https://sokra.github.io/source-map-visualization/#base64,"
257
+ )
258
+ end
259
+ end
260
+
239
261
  context 'using pipes' do
240
262
  it 'runs the provided source' do
241
263
  # `echo` on windows will output double-quotes along with the contents, that's why we print with ruby
@@ -0,0 +1 @@
1
+ var FILE_1;
@@ -0,0 +1 @@
1
+ var FILE_2;
@@ -0,0 +1 @@
1
+ var FILE_3;
@@ -0,0 +1 @@
1
+ var FILE_4;
@@ -0,0 +1,4 @@
1
+ # Try loading a file with an absolute path.
2
+ require "<%= p __dir__.inspect[1..-2] %>/file51.js"
3
+
4
+ FILE_5 = true
@@ -0,0 +1 @@
1
+ var FILE_51;
@@ -0,0 +1,10 @@
1
+ require_relative 'file61'
2
+
3
+ # There's a circular require between file63 and 62
4
+ # file62 should be in output file before 63
5
+ require 'fixtures/build_order/file63'
6
+ require_relative './file62'
7
+
8
+ require 'fixtures/build_order/file64.rb'
9
+
10
+ FILE_6 = true
@@ -0,0 +1 @@
1
+ FILE_61 = true
@@ -0,0 +1,4 @@
1
+ # A circular require
2
+ require_relative 'file63'
3
+
4
+ FILE_62 = true
@@ -0,0 +1,4 @@
1
+ # A circular require
2
+ require_relative 'file62'
3
+
4
+ FILE_63 = true
@@ -0,0 +1 @@
1
+ FILE_64 = true
@@ -0,0 +1 @@
1
+ FILE_7 = true
@@ -0,0 +1,9 @@
1
+ # This fixture tests build order of required JS files
2
+
3
+ require 'fixtures/build_order/file1'
4
+ require 'fixtures/build_order/file2'
5
+ require 'fixtures/build_order/file3'
6
+ require 'fixtures/build_order/file4'
7
+ require 'fixtures/build_order/file5'
8
+ require 'fixtures/build_order/file6'
9
+ require 'fixtures/build_order/file7'
@@ -0,0 +1,69 @@
1
+ require 'lib/spec_helper'
2
+ require 'open3'
3
+ require 'opal/os'
4
+
5
+ RSpec.describe "rake dist" do
6
+ before :all do
7
+ system "rake dist >#{Opal::OS.dev_null}"
8
+ end
9
+
10
+ def run_with_node(code, precode:, requires:)
11
+ requires = requires.map do |i|
12
+ "require('./build/#{i}');"
13
+ end.join
14
+
15
+ code = "#{requires};#{precode};console.log(#{code});"
16
+
17
+ stdout, _, status = Open3.capture3('node', '-e', code)
18
+
19
+ expect(status.exitstatus).to eq(0)
20
+
21
+ stdout.chomp
22
+ end
23
+
24
+ let(:output) { run_with_node(code, precode: precode, requires: requires) }
25
+ let(:requires) { ['opal'] }
26
+ let(:precode) { '' }
27
+ let(:code) { 'typeof Opal' }
28
+
29
+ it 'should provide a working Opal environment' do
30
+ expect(output).to eq('object')
31
+ end
32
+
33
+ context do
34
+ let(:requires) { ['opal/mini'] }
35
+
36
+ it 'should provide a working Opal mini environment' do
37
+ expect(output).to eq('object')
38
+ end
39
+ end
40
+
41
+ context do
42
+ let(:requires) { ['opal', 'opal/full'] }
43
+ let(:precode) { 'Opal.require("corelib/pattern_matching")' }
44
+ let(:code) { 'typeof Opal.PatternMatching' }
45
+
46
+ it 'should provide a working Opal full environment' do
47
+ expect(output).to eq('function')
48
+ end
49
+ end
50
+
51
+ context do
52
+ let(:requires) { %w[opal opal-replutils] }
53
+ let(:code) { 'typeof Opal.REPLUtils' }
54
+
55
+ it 'should not require requirable files by default' do
56
+ expect(output).to eq('undefined')
57
+ end
58
+ end
59
+
60
+ context do
61
+ let(:requires) { %w[opal opal-replutils] }
62
+ let(:precode) { 'Opal.require("opal-replutils")' }
63
+ let(:code) { 'typeof Opal.REPLUtils' }
64
+
65
+ it 'should allow user to require requirable files to provide missing functionality' do
66
+ expect(output).to eq('function')
67
+ end
68
+ end
69
+ end
@@ -7,6 +7,8 @@ end
7
7
 
8
8
  require 'opal'
9
9
 
10
+ ENV['OPAL_DISABLE_PREFORK_LOGS'] = '1'
11
+
10
12
  # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
11
13
  RSpec.configure do |config|
12
14
  config.before { Opal.reset_paths! unless RUBY_PLATFORM == 'opal' }
@@ -80,6 +80,7 @@ class OSpecFormatter
80
80
  'server' => BrowserFormatter,
81
81
  'chrome' => DottedFormatter,
82
82
  'firefox' => DottedFormatter,
83
+ 'safari' => DottedFormatter,
83
84
  'deno' => NodeJSFormatter,
84
85
  'node' => NodeJSFormatter,
85
86
  'nodejs' => NodeJSFormatter,
@@ -32,7 +32,10 @@ describe "IO reading methods" do
32
32
  examples.each do |example|
33
33
  it "correctly splits messages for input #{example.inspect}" do
34
34
  io = prepare_io_for.(example)
35
- expected_output = example.gsub("|", "").split(/(?<=\n)/)
35
+ expected_output = example.gsub("|", "").split(/\n/).map { |e| e + "\n" }
36
+ len = expected_output.length
37
+ last = expected_output.last
38
+ expected_output[len-1] = last.chop if !example.end_with?("\n") && last
36
39
  io.readlines.should == expected_output
37
40
  end
38
41
  end
@@ -42,7 +45,10 @@ describe "IO reading methods" do
42
45
  examples.each do |example|
43
46
  it "correctly splits messages for input #{example.inspect}" do
44
47
  io = prepare_io_for.(example)
45
- expected_output = example.gsub("|", "").split(/(?<=\n)/)
48
+ expected_output = example.gsub("|", "").split(/\n/).map { |e| e + "\n" }
49
+ len = expected_output.length
50
+ last = expected_output.last
51
+ expected_output[len-1] = last.chop if !example.end_with?("\n") && last
46
52
  loop do
47
53
  expected_output.shift.should == io.readline
48
54
  rescue EOFError
@@ -57,7 +63,10 @@ describe "IO reading methods" do
57
63
  examples.each do |example|
58
64
  it "correctly splits messages for input #{example.inspect}" do
59
65
  io = prepare_io_for.(example)
60
- expected_output = example.gsub("|", "").split(/(?<=\n)/)
66
+ expected_output = example.gsub("|", "").split(/\n/).map { |e| e + "\n" }
67
+ len = expected_output.length
68
+ last = expected_output.last
69
+ expected_output[len-1] = last.chop if !example.end_with?("\n") && last
61
70
  loop do
62
71
  line = io.gets
63
72
  expected_output.shift.should == line
@@ -8,5 +8,6 @@ when 'deno' then require 'deno/base'
8
8
  when 'nodejs' then require 'nodejs/base'
9
9
  when 'headless-chrome' then require 'headless_browser/base'
10
10
  when 'headless-firefox' then require 'headless_browser/base'
11
+ when 'safari' then require 'headless_browser/base'
11
12
  when 'opal-miniracer' then require 'opal/miniracer'
12
13
  end
@@ -6,6 +6,7 @@ node = `typeof(process) !== "undefined" && process.versions && proce
6
6
  nashorn = `typeof(Java) !== "undefined" && Java.type`
7
7
  headless_chrome = `typeof(opalheadlesschrome) !== "undefined"`
8
8
  headless_firefox = `typeof(opalheadlessfirefox) !== "undefined"`
9
+ safari = `typeof(opalsafari) !== "undefined"`
9
10
  gjs = `typeof(window) !== "undefined" && typeof(GjsFileImporter) !== "undefined"`
10
11
  quickjs = `typeof(window) === "undefined" && typeof(__loadScript) !== "undefined"`
11
12
  opal_miniracer = `typeof(opalminiracer) !== "undefined"`
@@ -20,6 +21,8 @@ OPAL_PLATFORM = if nashorn
20
21
  'headless-chrome'
21
22
  elsif headless_firefox
22
23
  'headless-firefox'
24
+ elsif safari
25
+ 'safari'
23
26
  elsif gjs
24
27
  'gjs'
25
28
  elsif quickjs