sunscraper 1.1.0.beta3 → 1.2.0.beta1

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 (39) hide show
  1. data/ext/common/common.pro +13 -0
  2. data/ext/common/libsunscraper_common.a +0 -0
  3. data/ext/common/sunscraperproxy.cpp +11 -0
  4. data/ext/{standalone → common}/sunscraperproxy.h +5 -5
  5. data/ext/{embed → common}/sunscraperwebpage.cpp +0 -0
  6. data/ext/{embed → common}/sunscraperwebpage.h +0 -0
  7. data/ext/common/sunscraperworker.cpp +124 -0
  8. data/ext/common/sunscraperworker.h +50 -0
  9. data/ext/embed/embed.pro +14 -12
  10. data/ext/embed/sunscraperexternal.cpp +17 -16
  11. data/ext/embed/sunscraperinterface.cpp +206 -0
  12. data/ext/embed/sunscraperinterface.h +66 -0
  13. data/ext/embed/sunscraperlibrary.cpp +2 -12
  14. data/ext/embed/sunscraperlibrary.h +0 -1
  15. data/ext/embed/sunscraperthread.cpp +49 -0
  16. data/ext/embed/sunscraperthread.h +24 -0
  17. data/ext/extconf.rb +5 -3
  18. data/ext/standalone/standalone.pro +12 -6
  19. data/ext/standalone/sunscrapermain.cpp +13 -3
  20. data/ext/standalone/sunscraperrpc.cpp +76 -88
  21. data/ext/standalone/sunscraperrpc.h +19 -22
  22. data/ext/standalone/sunscraperrpcserver.cpp +26 -0
  23. data/ext/standalone/sunscraperrpcserver.h +24 -0
  24. data/ext/sunscraper-ext.pro +1 -1
  25. data/lib/sunscraper.rb +14 -14
  26. data/lib/sunscraper/library.rb +9 -9
  27. data/lib/sunscraper/standalone.rb +53 -107
  28. data/spec/sunscraper_spec.rb +86 -44
  29. data/sunscraper.gemspec +1 -1
  30. metadata +19 -17
  31. data/ext/embed/sunscraper.cpp +0 -92
  32. data/ext/embed/sunscraper.h +0 -47
  33. data/ext/embed/sunscraperproxy.cpp +0 -14
  34. data/ext/embed/sunscraperproxy.h +0 -24
  35. data/ext/embed/sunscraperworker.cpp +0 -163
  36. data/ext/embed/sunscraperworker.h +0 -58
  37. data/ext/standalone/sunscraperproxy.cpp +0 -14
  38. data/ext/standalone/sunscraperworker.cpp +0 -60
  39. data/ext/standalone/sunscraperworker.h +0 -34
@@ -7,7 +7,6 @@
7
7
 
8
8
  class SunscraperWorker;
9
9
  class QLocalSocket;
10
- class QTimer;
11
10
 
12
11
  class SunscraperRPC : public QObject
13
12
  {
@@ -18,47 +17,45 @@ class SunscraperRPC : public QObject
18
17
  StateData,
19
18
  };
20
19
 
21
- struct Header {
22
- quint32 queryId;
23
- quint32 requestType;
24
- quint32 dataLength;
25
- };
26
-
27
20
  enum Request {
28
- RPC_LOAD_HTML = 1,
29
- RPC_LOAD_URL = 2,
21
+ RPC_LOAD_URL = 1,
22
+ RPC_LOAD_HTML = 2,
30
23
  RPC_WAIT = 3,
31
24
  RPC_FETCH = 4,
32
- RPC_DISCARD = 5,
33
25
  };
34
26
 
35
27
  public:
36
- SunscraperRPC(QString socketPath);
28
+ SunscraperRPC(QLocalSocket *socket);
37
29
  ~SunscraperRPC();
38
30
 
31
+ signals:
32
+ void disconnected();
33
+
39
34
  private slots:
40
35
  void onInputReadable();
41
36
  void onInputDisconnected();
42
- void onPageRendered(unsigned queryId, QString data);
43
- void onTimeout();
37
+
38
+ void onFinish(unsigned queryId);
39
+ void onTimeout(unsigned queryId);
40
+ void onFetchDone(unsigned queryId, QString data);
44
41
 
45
42
  private:
43
+ static unsigned m_nextQueryId;
44
+ static SunscraperWorker *m_worker;
45
+
46
+ unsigned m_queryId;
46
47
  QLocalSocket *m_socket;
47
48
 
48
- State m_state;
49
- Header m_pendingHeader;
49
+ State m_state;
50
+ unsigned m_pendingRequest, m_pendingDataLength;
50
51
  QByteArray m_buffer;
51
52
 
52
- SunscraperWorker *m_worker;
53
-
54
- QList<unsigned> m_waitQueue;
55
- QMap<unsigned, QTimer*> m_timers;
56
- QMap<unsigned, QString> m_results;
53
+ bool m_result;
57
54
 
58
55
  SunscraperRPC();
59
56
 
60
- void processRequest(Header header, QByteArray data);
61
- void sendReply(Header header, QByteArray data);
57
+ void processRequest(unsigned requestType, QByteArray data);
58
+ void sendReply(QByteArray data);
62
59
  };
63
60
 
64
61
  #endif // SUNSCRAPERRPC_H
@@ -0,0 +1,26 @@
1
+ #include <QLocalServer>
2
+ #include "sunscraperrpcserver.h"
3
+ #include "sunscraperrpc.h"
4
+
5
+ SunscraperRPCServer::SunscraperRPCServer(QObject *parent) :
6
+ QObject(parent)
7
+ {
8
+ m_localServer = new QLocalServer();
9
+
10
+ connect(m_localServer, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
11
+ }
12
+
13
+ bool SunscraperRPCServer::listen(QString socketPath)
14
+ {
15
+ return m_localServer->listen(socketPath);
16
+ }
17
+
18
+ void SunscraperRPCServer::onNewConnection()
19
+ {
20
+ while(m_localServer->hasPendingConnections()) {
21
+ QLocalSocket *socket = m_localServer->nextPendingConnection();
22
+
23
+ SunscraperRPC *rpc = new SunscraperRPC(socket);
24
+ connect(rpc, SIGNAL(disconnected()), rpc, SLOT(deleteLater()));
25
+ }
26
+ }
@@ -0,0 +1,24 @@
1
+ #ifndef SUNSCRAPERRPCSERVER_H
2
+ #define SUNSCRAPERRPCSERVER_H
3
+
4
+ #include <QObject>
5
+
6
+ class QLocalServer;
7
+
8
+ class SunscraperRPCServer : public QObject
9
+ {
10
+ Q_OBJECT
11
+
12
+ public:
13
+ SunscraperRPCServer(QObject *parent = 0);
14
+
15
+ bool listen(QString socketPath);
16
+
17
+ private slots:
18
+ void onNewConnection();
19
+
20
+ private:
21
+ QLocalServer *m_localServer;
22
+ };
23
+
24
+ #endif
@@ -1,2 +1,2 @@
1
1
  TEMPLATE = subdirs
2
- SUBDIRS = embed standalone
2
+ SUBDIRS = common embed standalone
data/lib/sunscraper.rb CHANGED
@@ -1,5 +1,5 @@
1
- if !defined?(RUBY_ENGINE) && RUBY_VERSION =~ /^1.8/
2
- raise RuntimeError, "Sunscraper does not work on Ruby MRI 1.8.x."
1
+ if RUBY_VERSION =~ /^1.8/
2
+ raise RuntimeError, "Sunscraper does not work on Ruby 1.8."
3
3
  end
4
4
 
5
5
  # Sunscraper loads an HTML page in a headless browser and waits for `Sunscraper.finish()`
@@ -27,9 +27,9 @@ module Sunscraper
27
27
  # If your application depends on base URL being available, use {scrape_url}.
28
28
  #
29
29
  # @param [Integer] timeout timeout in milliseconds
30
- def scrape_html(html, timeout=5000)
30
+ def scrape_html(html, url="about:blank", timeout=5000)
31
31
  scrape(timeout) do |worker, context|
32
- worker.load_html context, html
32
+ worker.load_html context, html, url
33
33
  end
34
34
  end
35
35
 
@@ -47,19 +47,19 @@ module Sunscraper
47
47
  def scrape(timeout)
48
48
  worker = load_worker
49
49
 
50
- context = worker.create
51
- yield worker, context
52
- worker.wait(context, timeout)
50
+ begin
51
+ context = worker.create
53
52
 
54
- data = worker.fetch(context)
53
+ yield worker, context
55
54
 
56
- if data == "!SUNSCRAPER_TIMEOUT"
57
- raise ScrapeTimeout, "Sunscraper has timed out waiting for the callback"
58
- else
59
- data
55
+ if worker.wait(context, timeout)
56
+ worker.fetch(context)
57
+ else
58
+ raise ScrapeTimeout, "Sunscraper has timed out waiting for the callback"
59
+ end
60
+ ensure
61
+ worker.finalize(context) if context
60
62
  end
61
- ensure
62
- worker.discard(context) if context
63
63
  end
64
64
 
65
65
  def load_worker
@@ -19,23 +19,23 @@ module Sunscraper
19
19
  ffi_lib File.join(Gem.loaded_specs['sunscraper'].full_gem_path,
20
20
  'ext', 'embed', "libsunscraper.#{extension}")
21
21
 
22
- attach_function 'create', :sunscraper_create, [], :pointer
23
- attach_function 'load_html', :sunscraper_load_html, [:pointer, :string], :void
24
- attach_function 'load_url', :sunscraper_load_url, [:pointer, :string], :void
25
- attach_function 'fetch', :sunscraper_fetch, [:pointer], :string
26
- attach_function 'discard', :sunscraper_discard, [:pointer], :void
22
+ attach_function 'create', :sunscraper_create, [], :uint
23
+ attach_function 'load_html', :sunscraper_load_html, [:uint, :string, :string], :void
24
+ attach_function 'load_url', :sunscraper_load_url, [:uint, :string], :void
25
+ attach_function 'fetch', :sunscraper_fetch, [:uint], :string
26
+ attach_function 'finalize', :sunscraper_finalize, [:uint], :void
27
27
 
28
28
  if RUBY_ENGINE == 'ruby'
29
29
  # MRI uses ffi gem and has GVL. Hence, it needs a rb_thread_blocking_region call.
30
- attach_function 'wait', :sunscraper_wait, [:pointer, :uint], :void, :blocking => true
30
+ attach_function 'wait', :sunscraper_wait, [:uint, :uint], :bool, :blocking => true
31
31
  else
32
32
  # Rubinius does not have GVL neither it has options in attach_function.
33
33
  # Same for JRuby.
34
- attach_function 'wait', :sunscraper_wait, [:pointer, :uint], :void
34
+ attach_function 'wait', :sunscraper_wait, [:uint, :uint], :bool
35
35
  end
36
36
 
37
- attach_function 'finalize', :sunscraper_finalize, [], :void
37
+ attach_function 'quit', :sunscraper_quit, [], :void
38
38
 
39
- at_exit { finalize }
39
+ at_exit { quit }
40
40
  end
41
41
  end
@@ -3,113 +3,65 @@ require 'socket'
3
3
  # @private
4
4
  module Sunscraper
5
5
  module Standalone
6
- @last_query_id = 0
6
+ @rpc_mutex = Mutex.new
7
+ @rpc_socket = nil
7
8
 
8
- @rpc_mutex = Mutex.new
9
- @rpc_waiters = {}
10
- @rpc_results = {}
11
- @rpc_thread = nil
12
-
13
- RPC_LOAD_HTML = 1
14
- RPC_LOAD_URL = 2
9
+ RPC_LOAD_URL = 1
10
+ RPC_LOAD_HTML = 2
15
11
  RPC_WAIT = 3
16
12
  RPC_FETCH = 4
17
- RPC_DISCARD = 5
18
13
 
19
14
  class << self
20
- attr_reader :rpc_mutex, :rpc_waiters, :rpc_results
21
-
22
15
  def create
23
- @rpc_mutex.synchronize do
24
- @last_query_id += 1
25
- @last_query_id
26
- end
27
- end
28
-
29
- def load_html(query_id, html)
30
- perform_rpc query_id,
31
- request: RPC_LOAD_HTML,
32
- data: html
16
+ connect_to_worker
33
17
  end
34
18
 
35
- def load_url(query_id, url)
36
- perform_rpc query_id,
19
+ def load_url(socket, url)
20
+ perform_rpc socket,
37
21
  request: RPC_LOAD_URL,
38
22
  data: url
39
23
  end
40
24
 
41
- def wait(query_id, timeout)
42
- perform_rpc query_id,
25
+ def load_html(socket, html, baseUrl)
26
+ html, baseUrl = [html, baseUrl].map(&:to_s)
27
+ perform_rpc socket,
28
+ request: RPC_LOAD_HTML,
29
+ data: [html.length, html, baseUrl.length, baseUrl].pack("Na*Na*")
30
+ end
31
+
32
+ def wait(socket, timeout)
33
+ result = perform_rpc socket,
43
34
  request: RPC_WAIT,
44
35
  data: [timeout].pack("N"),
45
36
  want_result: true
37
+ code, = result.unpack("N")
38
+
39
+ code == 1 # true
46
40
  end
47
41
 
48
- def fetch(query_id)
49
- perform_rpc query_id,
42
+ def fetch(socket)
43
+ perform_rpc socket,
50
44
  request: RPC_FETCH,
51
45
  want_result: true
52
46
  end
53
47
 
54
- def discard(query_id)
55
- perform_rpc query_id,
56
- request: RPC_DISCARD
48
+ def finalize(socket)
49
+ socket.close
57
50
  end
58
51
 
59
52
  private
60
53
 
61
- def perform_rpc(query_id, options={})
54
+ def perform_rpc(socket, options={})
62
55
  data = options[:data] || ""
63
- block = options[:want_result]
64
-
65
- @rpc_mutex.synchronize do
66
- if @rpc_thread.nil?
67
- @rpc_thread = Standalone::Thread.new(::Thread.current)
68
-
69
- # Some fucko decided not to put any semaphores in Ruby,
70
- # _and_ restrict Mutexes to be unlocked only from the thread
71
- # which has locked them.
72
- #
73
- # Please, kill yourself if you're reading this.
74
- ::Thread.stop
75
- end
76
-
77
- @rpc_thread.perform(query_id, options[:request], data)
78
-
79
- if block
80
- @rpc_waiters[query_id] = Thread.current
81
- end
82
- end
83
-
84
- if block
85
- Thread.stop
86
- @rpc_results[query_id]
87
- end
88
- ensure
89
- if block
90
- @rpc_waiters.delete query_id
91
- @rpc_results.delete query_id
92
- end
93
- end
94
- end
95
-
96
- class Thread < ::Thread
97
- def initialize(creator)
98
- @creator = creator
56
+ socket.write([options[:request], data.length, data].pack("NNa*"))
99
57
 
100
- super do
101
- @parent = Sunscraper::Standalone
102
- work
58
+ if options[:want_result]
59
+ data_length, = socket.read(4).unpack("N")
60
+ socket.read(data_length)
103
61
  end
104
62
  end
105
63
 
106
- def perform(query_id, request, data)
107
- @socket.write([query_id, request, data.length, data].pack("NNNa*"))
108
- end
109
-
110
- private
111
-
112
- def work
64
+ def spawn_worker
113
65
  if ::Sunscraper.os_x?
114
66
  # Fuck you, OS X.
115
67
  suffix = ".app/Contents/MacOS/sunscraper"
@@ -121,47 +73,41 @@ module Sunscraper
121
73
  'ext', 'standalone', "sunscraper#{suffix}")
122
74
 
123
75
  server_path = "/tmp/sunscraper.#{Process.pid}.sock"
124
- server = UNIXServer.new(server_path)
76
+ File.unlink server_path if File.exists? server_path
125
77
 
126
78
  if Kernel.respond_to? :spawn
127
- pid = Kernel.spawn "#{executable} #{server_path}"
79
+ @rpc_pid = Kernel.spawn "#{executable} #{server_path}"
128
80
  else
129
81
  # rbx does not have Kernel.spawn (yet). Sigh...
130
- pid = fork { exec executable, server_path }
82
+ @rpc_pid = fork { exec executable, server_path }
131
83
  end
132
84
 
133
- Process.detach pid
134
-
135
- @socket = server.accept
85
+ # Sigh again. Probably no other way.
86
+ loop do
87
+ if File.exists? server_path
88
+ @rpc_socket = server_path
89
+ break
90
+ elsif Process.wait(@rpc_pid, Process::WNOHANG)
91
+ raise RuntimeError, "Cannot start Sunscraper process"
92
+ end
136
93
 
137
- server.close
138
- FileUtils.rm server_path
94
+ sleep 0.1
95
+ end
139
96
 
140
- # See above.
141
- @creator.wakeup
97
+ Process.detach @rpc_pid
142
98
 
143
- loop do
144
- header = @socket.read(4 * 3)
145
- query_id, request, data_length = header.unpack("NNN")
146
- data = @socket.read(data_length) if data_length > 0
147
-
148
- @parent.rpc_mutex.synchronize do
149
- if !@parent.rpc_waiters.include?(query_id)
150
- $stderr.puts "Sunscraper/standalone: no waiter for #{query_id}"
151
- else
152
- @parent.rpc_results[query_id] = data
153
- @parent.rpc_waiters[query_id].wakeup
154
- end
155
- end
99
+ at_exit do
100
+ Process.kill "KILL", @rpc_pid
101
+ File.unlink @rpc_socket
156
102
  end
157
- rescue Exception => e
158
- $stderr.puts "Sunscraper error: #{e.class}: #{e.message}"
159
- e.backtrace.each do |line|
160
- $stderr.puts " #{line}"
103
+ end
104
+
105
+ def connect_to_worker
106
+ @rpc_mutex.synchronize do
107
+ spawn_worker if @rpc_socket.nil?
161
108
  end
162
- ensure
163
- @socket.close
164
- Process.kill pid
109
+
110
+ UNIXSocket.new(@rpc_socket)
165
111
  end
166
112
  end
167
113
  end
@@ -1,15 +1,12 @@
1
1
  require 'spec_helper'
2
-
3
2
  require 'webrick'
4
3
 
5
- HTML = <<HTML
4
+ HTML_TEMPLATE = <<HTML
6
5
  <html>
7
6
  <head>
8
7
  <script type="text/javascript">
9
8
  document.addEventListener("DOMContentLoaded", function() {
10
- document.getElementById('fuga').textContent =
11
- ("!skrow tI").split("").reverse().join("");
12
- Sunscraper.finish();
9
+ %code%
13
10
  }, true);
14
11
  </script>
15
12
  </head>
@@ -19,73 +16,118 @@ HTML = <<HTML
19
16
  </html>
20
17
  HTML
21
18
 
22
- PORT = 45555
19
+ HTML_FUGA = HTML_TEMPLATE.sub("%code%", <<CODE)
20
+ document.getElementById('fuga').textContent =
21
+ ("!skrow tI").split("").reverse().join("");
22
+ Sunscraper.finish();
23
+ CODE
24
+
25
+ HTML_BASEURL = HTML_TEMPLATE.sub("%code%", <<CODE)
26
+ var xhr = new XMLHttpRequest();
27
+ xhr.onreadystatechange = function() {
28
+ if(xhr.readyState > 3) {
29
+ document.getElementById('fuga').textContent = xhr.responseText;
30
+ Sunscraper.finish();
31
+ }
32
+ };
33
+ xhr.open('GET', '/comicstrip', 1);
34
+ xhr.send();
35
+ CODE
36
+
37
+ HTML_USERAGENT = HTML_TEMPLATE.sub("%code%", <<CODE)
38
+ document.getElementById('fuga').textContent =
39
+ window.navigator.userAgent;
40
+ Sunscraper.finish();
41
+ CODE
42
+
43
+ HTML_LOCALSTORAGE = HTML_TEMPLATE.sub("%code%", <<CODE)
44
+ window.localStorage.setItem("key", ["O", "K"].join(""))
45
+ document.getElementById('fuga').textContent =
46
+ window.localStorage.getItem("key");
47
+ Sunscraper.finish();
48
+ CODE
23
49
 
24
- def with_webserver
25
- server = WEBrick::HTTPServer.new :Port => PORT, :Logger => WEBrick::Log.new('/dev/null'), :AccessLog => []
50
+ def with_webserver(html)
51
+ port = 45555
52
+ server = WEBrick::HTTPServer.new :Port => port, :Logger => WEBrick::Log.new('/dev/null'), :AccessLog => []
26
53
  server.mount_proc '/' do |req, res|
27
- res.body = HTML
54
+ res.body = html
28
55
  end
29
- Thread.new { server.start }
56
+ server.mount_proc '/comicstrip' do |req, res|
57
+ res.body = 'Go Get a Roomie!'
58
+ end
59
+ thread = Thread.new { server.start }
30
60
 
31
- yield PORT
61
+ yield "http://127.0.0.1:#{port}/"
32
62
  ensure
33
- server.shutdown if server
63
+ server.shutdown
64
+ thread.join
34
65
  end
35
66
 
36
- class String
37
- def to_v
38
- split(".").map(&:to_i).extend Comparable
39
- end
40
- end
67
+ define_tests = lambda do |klass, worker|
68
+ describe klass do
69
+ before(:all) do
70
+ Sunscraper.worker = worker
71
+ end
41
72
 
42
- unless Sunscraper.os_x?
43
- # This part currently crashes on OS X (and will forever).
44
- describe "Sunscraper::Library" do
45
- before do
46
- Sunscraper.worker = :embed
73
+ after(:all) do
74
+ sleep(5) # let threads rest in peace
47
75
  end
48
76
 
49
77
  it "can scrape an HTML provided as a string" do
50
- Sunscraper.scrape_html(HTML).should include('It works!')
78
+ Sunscraper.scrape_html(HTML_FUGA).should include('It works!')
51
79
  end
52
80
 
53
81
  it "can scrape an URL" do
54
- with_webserver do |port|
55
- Sunscraper.scrape_url("http://127.0.0.1:#{port}/").should include('It works!')
82
+ with_webserver(HTML_FUGA) do |url|
83
+ Sunscraper.scrape_url(url).should include('It works!')
56
84
  end
57
85
  end
58
86
 
59
87
  it "should time out if callback is not called" do
60
- lambda { Sunscraper.scrape_html("<!-- nothing. at least no callbacks -->", 1000) }.
88
+ lambda { Sunscraper.scrape_html("<!-- nothing. at least no callbacks -->", "about:blank", 500) }.
61
89
  should raise_exception(Sunscraper::ScrapeTimeout)
62
90
  end
63
- end
64
- end
65
91
 
66
- if !(RUBY_ENGINE =~ /rbx/ || RUBY_ENGINE =~ /jruby/) ||
67
- ENV['EXPERIMENTAL'] == 'true'
68
- # This part currently crashes Rubinius (as of Mar 09, 2012),
69
- # and crashes jruby < 1.7.0, and uses Unix sockets which don't
70
- # work even on jruby master (as of Mar 09, 2012).
71
- describe "Sunscraper::Standalone" do
72
- before do
73
- Sunscraper.worker = :standalone
92
+ it "respects baseUrl parameter" do
93
+ with_webserver("<!-- nothing -->") do |url|
94
+ Sunscraper.scrape_html(HTML_BASEURL, url).should include('Go Get a Roomie')
95
+ end
74
96
  end
75
97
 
76
- it "can scrape an HTML provided as a string" do
77
- Sunscraper.scrape_html(HTML).should include('It works!')
98
+ it "should identify itself as Sunscraper" do
99
+ Sunscraper.scrape_html(HTML_USERAGENT).should include("Sunscraper")
78
100
  end
79
101
 
80
- it "can scrape an URL" do
81
- with_webserver do |port|
82
- Sunscraper.scrape_url("http://127.0.0.1:#{port}/").should include('It works!')
102
+ it "should work with window.localStorage through webserver" do
103
+ with_webserver(HTML_LOCALSTORAGE) do |url|
104
+ Sunscraper.scrape_url(url).should include("OK")
83
105
  end
84
106
  end
85
107
 
86
- it "should time out if callback is not called" do
87
- lambda { Sunscraper.scrape_html("<!-- nothing. at least no callbacks -->", 1000) }.
88
- should raise_exception(Sunscraper::ScrapeTimeout)
108
+ it "should withstand a lot of concurrent threads" do
109
+ 500.times.map {
110
+ Thread.new {
111
+ Sunscraper.scrape_html(HTML_FUGA)
112
+ }
113
+ }.each(&:join).
114
+ map(&:value).
115
+ each { |result|
116
+ result.should include('It works!')
117
+ }
89
118
  end
90
119
  end
120
+ end
121
+
122
+ unless Sunscraper.os_x?
123
+ # This part currently crashes on OS X (and will forever).
124
+ define_tests.("Sunscraper-Embed", :embed)
125
+ end
126
+
127
+ if !(RUBY_ENGINE =~ /rbx/ || RUBY_ENGINE =~ /jruby/) ||
128
+ ENV['EXPERIMENTAL'] == 'true'
129
+ # This part currently crashes Rubinius (as of Mar 09, 2012),
130
+ # and crashes jruby < 1.7.0, and uses Unix sockets which don't
131
+ # work even on jruby master (as of Mar 09, 2012).
132
+ define_tests.("Sunscraper-Standalone", :standalone)
91
133
  end