asir 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (121) hide show
  1. data/.gitignore +11 -0
  2. data/Gemfile +16 -0
  3. data/README.textile +50 -0
  4. data/Rakefile +83 -0
  5. data/VERSION +1 -0
  6. data/asir.gemspec +36 -0
  7. data/asir.riterate.yml +114 -0
  8. data/bin/asir +6 -0
  9. data/doc/Rakefile +8 -0
  10. data/doc/asir-sequence.pic +84 -0
  11. data/doc/asir-sequence.svg +1559 -0
  12. data/doc/sequence.pic +430 -0
  13. data/example/asir_control.sh +24 -0
  14. data/example/asir_control_client_http.rb +14 -0
  15. data/example/asir_control_client_zmq.rb +15 -0
  16. data/example/config/asir_config.rb +63 -0
  17. data/example/delayed_service.rb +15 -0
  18. data/example/ex01.rb +12 -0
  19. data/example/ex02.rb +12 -0
  20. data/example/ex03.rb +19 -0
  21. data/example/ex04.rb +33 -0
  22. data/example/ex05.rb +16 -0
  23. data/example/ex06.rb +26 -0
  24. data/example/ex07.rb +28 -0
  25. data/example/ex08.rb +30 -0
  26. data/example/ex09.rb +25 -0
  27. data/example/ex10.rb +24 -0
  28. data/example/ex11.rb +48 -0
  29. data/example/ex12.rb +34 -0
  30. data/example/ex13.rb +35 -0
  31. data/example/ex14.rb +30 -0
  32. data/example/ex15.rb +13 -0
  33. data/example/ex16.rb +33 -0
  34. data/example/ex17.rb +41 -0
  35. data/example/ex18.rb +62 -0
  36. data/example/ex19.rb +32 -0
  37. data/example/ex20.rb +28 -0
  38. data/example/ex21.rb +28 -0
  39. data/example/ex22.rb +15 -0
  40. data/example/ex23.rb +20 -0
  41. data/example/ex24.rb +35 -0
  42. data/example/example_helper.rb +51 -0
  43. data/example/sample_service.rb +162 -0
  44. data/example/unsafe_service.rb +12 -0
  45. data/hack_night/README.txt +18 -0
  46. data/hack_night/exercise/prob-1.rb +18 -0
  47. data/hack_night/exercise/prob-2.rb +21 -0
  48. data/hack_night/exercise/prob-3.rb +16 -0
  49. data/hack_night/exercise/prob-4.rb +36 -0
  50. data/hack_night/exercise/prob-5.rb +36 -0
  51. data/hack_night/exercise/prob-6.rb +95 -0
  52. data/hack_night/exercise/prob-7.rb +34 -0
  53. data/hack_night/solution/math_service.rb +11 -0
  54. data/hack_night/solution/prob-1.rb +12 -0
  55. data/hack_night/solution/prob-2.rb +15 -0
  56. data/hack_night/solution/prob-3.rb +17 -0
  57. data/hack_night/solution/prob-4.rb +37 -0
  58. data/hack_night/solution/prob-5.rb +21 -0
  59. data/hack_night/solution/prob-6.rb +33 -0
  60. data/hack_night/solution/prob-7.rb +36 -0
  61. data/lab/phony_proc.rb +31 -0
  62. data/lib/asir.rb +253 -0
  63. data/lib/asir/additional_data.rb +25 -0
  64. data/lib/asir/channel.rb +130 -0
  65. data/lib/asir/client.rb +111 -0
  66. data/lib/asir/code_block.rb +57 -0
  67. data/lib/asir/code_more.rb +50 -0
  68. data/lib/asir/coder.rb +26 -0
  69. data/lib/asir/coder/base64.rb +19 -0
  70. data/lib/asir/coder/chain.rb +30 -0
  71. data/lib/asir/coder/identity.rb +23 -0
  72. data/lib/asir/coder/json.rb +30 -0
  73. data/lib/asir/coder/marshal.rb +17 -0
  74. data/lib/asir/coder/null.rb +23 -0
  75. data/lib/asir/coder/proc.rb +22 -0
  76. data/lib/asir/coder/sign.rb +48 -0
  77. data/lib/asir/coder/xml.rb +213 -0
  78. data/lib/asir/coder/yaml.rb +33 -0
  79. data/lib/asir/coder/zlib.rb +21 -0
  80. data/lib/asir/configuration.rb +32 -0
  81. data/lib/asir/error.rb +34 -0
  82. data/lib/asir/identity.rb +36 -0
  83. data/lib/asir/initialization.rb +23 -0
  84. data/lib/asir/log.rb +82 -0
  85. data/lib/asir/main.rb +396 -0
  86. data/lib/asir/message.rb +31 -0
  87. data/lib/asir/message/delay.rb +35 -0
  88. data/lib/asir/object_resolving.rb +15 -0
  89. data/lib/asir/result.rb +39 -0
  90. data/lib/asir/retry_behavior.rb +54 -0
  91. data/lib/asir/transport.rb +241 -0
  92. data/lib/asir/transport/beanstalk.rb +217 -0
  93. data/lib/asir/transport/broadcast.rb +34 -0
  94. data/lib/asir/transport/buffer.rb +115 -0
  95. data/lib/asir/transport/composite.rb +19 -0
  96. data/lib/asir/transport/connection_oriented.rb +180 -0
  97. data/lib/asir/transport/delay.rb +38 -0
  98. data/lib/asir/transport/delegation.rb +53 -0
  99. data/lib/asir/transport/fallback.rb +36 -0
  100. data/lib/asir/transport/file.rb +88 -0
  101. data/lib/asir/transport/http.rb +54 -0
  102. data/lib/asir/transport/local.rb +21 -0
  103. data/lib/asir/transport/null.rb +14 -0
  104. data/lib/asir/transport/payload_io.rb +52 -0
  105. data/lib/asir/transport/rack.rb +73 -0
  106. data/lib/asir/transport/retry.rb +41 -0
  107. data/lib/asir/transport/stream.rb +35 -0
  108. data/lib/asir/transport/subprocess.rb +30 -0
  109. data/lib/asir/transport/tcp_socket.rb +34 -0
  110. data/lib/asir/transport/webrick.rb +50 -0
  111. data/lib/asir/transport/zmq.rb +110 -0
  112. data/lib/asir/uuid.rb +32 -0
  113. data/lib/asir/version.rb +3 -0
  114. data/spec/const_get_speed_spec.rb +33 -0
  115. data/spec/debug_helper.rb +20 -0
  116. data/spec/example_spec.rb +88 -0
  117. data/spec/json_spec.rb +128 -0
  118. data/spec/spec_helper.rb +3 -0
  119. data/spec/xml_spec.rb +144 -0
  120. data/stylesheets/slides.css +105 -0
  121. metadata +173 -0
@@ -0,0 +1,14 @@
1
+ require 'example_helper'
2
+ require 'asir/transport/http'
3
+ require 'asir/coder/marshal'
4
+ begin
5
+ Email.client.transport = t =
6
+ ASIR::Transport::HTTP.new(:uri => "http://localhost:30000/asir")
7
+ t.encoder = ASIR::Coder::Marshal.new
8
+ pr Email.client.send_email(:pdf_invoice,
9
+ :to => "user@email.com",
10
+ :customer => @customer)
11
+ ensure
12
+ t.close rescue nil
13
+ end
14
+
@@ -0,0 +1,15 @@
1
+ require 'example_helper'
2
+ require 'asir/transport/zmq'
3
+ require 'asir/coder/marshal'
4
+ begin
5
+ Email.client.transport = t =
6
+ ASIR::Transport::Zmq.new :uri => "tcp://localhost:31000" # "/asir"
7
+ t.one_way = true
8
+ t.encoder = ASIR::Coder::Marshal.new
9
+ pr Email.client.send_email(:pdf_invoice,
10
+ :to => "user@email.com",
11
+ :customer => @customer)
12
+ ensure
13
+ t.close rescue nil
14
+ end
15
+
@@ -0,0 +1,63 @@
1
+ # Used by asir/bin/asir.
2
+ # Configures asir worker transport and error logging.
3
+ # asir object is bound to ASIR::Main instance.
4
+
5
+ $stderr.puts "asir.verb = #{asir.verb.inspect}"
6
+ case asir.verb
7
+ when :config
8
+ # NOTHING
9
+ true
10
+ when :environment
11
+ require 'asir'
12
+ require 'asir/transport/file'
13
+ require 'asir/coder/marshal'
14
+ require 'asir/coder/yaml'
15
+
16
+ $:.unshift File.expand_path('..')
17
+ require 'example_helper'
18
+ require 'sample_service'
19
+ require 'unsafe_service'
20
+ when :transport
21
+ # Compose with Marshal for final coding.
22
+ coder = ASIR::Coder::Marshal.new
23
+
24
+ # Logger for worker-side Exceptions.
25
+ error_log_file = asir.log_file.sub(/\.log$/, '-error.log')
26
+ error_transport =
27
+ ASIR::Transport::File.new(:file => error_log_file,
28
+ :mode => 'a+',
29
+ :perms => 0666)
30
+ error_transport.encoder = ASIR::Coder::Yaml.new
31
+
32
+ # Setup requested Transport.
33
+ case asir.adjective
34
+ when :beanstalk
35
+ transport = ASIR::Transport::Beanstalk.new
36
+ transport[:worker_processes] = 3
37
+ when :http, :webrick
38
+ transport = ASIR::Transport::Webrick.new
39
+ transport.uri = "http://localhost:#{30000 + asir.identifier.to_s.to_i}/asir"
40
+ when :rack
41
+ transport = ASIR::Transport::Rack.new
42
+ transport.uri = "http://localhost:#{30000 + asir.identifier.to_s.to_i}/asir"
43
+ when :zmq
44
+ transport = ASIR::Transport::Zmq.new
45
+ transport.one_way = true
46
+ transport.uri = "tcp://localhost:#{31000 + asir.identifier.to_s.to_i}" # /asir"
47
+ else
48
+ raise "Cannot configure Transport for #{asir.adjective}"
49
+ end
50
+
51
+ transport.encoder = coder
52
+ transport._logger = STDERR
53
+ transport._log_enabled = true
54
+ transport.verbose = 3
55
+ transport.on_exception =
56
+ lambda { | transport, exc, phase, message, *rest |
57
+ error_transport.send_request(message)
58
+ }
59
+
60
+ transport
61
+ else
62
+ $stderr.puts "Warning: unhandled asir.verb: #{asir.verb.inspect}"
63
+ end
@@ -0,0 +1,15 @@
1
+ require 'asir'
2
+ require 'time'
3
+
4
+ module DelayedService
5
+ include ASIR::Client
6
+ def self.do_it(t0)
7
+ dt = Time.now - t0
8
+ result = 5 <= dt && dt <= 6 ? :ok : :not_delayed
9
+ puts "DelayedService.do_it(#{t0.iso8601}) dt=#{dt.inspect} #{result.inspect}"
10
+ $stderr.puts "DelayedService.do_it => #{result.inspect}"
11
+ raise "Failed" unless result == :ok
12
+ result
13
+ end
14
+ end
15
+
@@ -0,0 +1,12 @@
1
+ # !SLIDE :capture_code_output true
2
+ # Call service directly
3
+
4
+ require 'example_helper' # !COMMENT
5
+ pr Email.send_email(:pdf_invoice,
6
+ :to => "user@email.com",
7
+ :customer => @customer)
8
+
9
+ # !SLIDE END
10
+ # EXPECT: : client process
11
+ # EXPECT: : Email.send_mail :pdf_invoice
12
+ # EXPECT: : pr: :ok
@@ -0,0 +1,12 @@
1
+ # !SLIDE :capture_code_output true
2
+ # In-core, in-process service
3
+
4
+ require 'example_helper'
5
+ pr Email.client.send_email(:pdf_invoice,
6
+ :to => "user@email.com",
7
+ :customer => @customer)
8
+
9
+ # !SLIDE END
10
+ # EXPECT: : client process
11
+ # EXPECT: : Email.send_mail :pdf_invoice
12
+ # EXPECT: : pr: :ok
@@ -0,0 +1,19 @@
1
+ #
2
+ # !SLIDE :capture_code_output true
3
+ # One-way, asynchronous subprocess service
4
+
5
+ require 'example_helper'
6
+ begin
7
+ Email.client.transport = t =
8
+ ASIR::Transport::Subprocess.new
9
+
10
+ pr Email.client.send_email(:pdf_invoice,
11
+ :to => "user@email.com",
12
+ :customer => @customer)
13
+ end
14
+
15
+ # !SLIDE END
16
+ # EXPECT: : client process
17
+ # EXPECT: : Email.send_mail :pdf_invoice
18
+ # EXPECT: : pr: nil
19
+
@@ -0,0 +1,33 @@
1
+ # !SLIDE :capture_code_output true
2
+ # One-way, file log service
3
+
4
+ require 'example_helper'
5
+ begin
6
+ File.unlink(service_log = "#{__FILE__}.service.log") rescue nil
7
+ Email.client.transport = t =
8
+ ASIR::Transport::File.new(:file => service_log)
9
+ t.encoder =
10
+ ASIR::Coder::Yaml.new
11
+ pr Email.client.send_email(:pdf_invoice,
12
+ :to => "user@email.com",
13
+ :customer => @customer)
14
+ ensure
15
+ t.close
16
+ puts "\x1a\n#{service_log.inspect} contents:"
17
+ puts File.read(service_log)
18
+ end
19
+
20
+ # !SLIDE END
21
+ # EXPECT: : client process
22
+ # EXPECT: service.log" contents:
23
+ # EXPECT: pr: nil
24
+ # EXPECT: --- !ruby/object:ASIR::Message
25
+ # EXPECT: --- !ruby/object:ASIR::Message
26
+ # EXPECT: arguments:
27
+ # EXPECT: - :pdf_invoice
28
+ # EXPECT: :to: user@email.com
29
+ # EXPECT: :customer: 123
30
+ # EXPECT: receiver: Email
31
+ # EXPECT: receiver_class: Module
32
+ # EXPECT: selector: :send_email
33
+
@@ -0,0 +1,16 @@
1
+ # !SLIDE :capture_code_output true
2
+ # Replay file log
3
+
4
+ require 'example_helper'
5
+ begin
6
+ service_log = "#{__FILE__.sub('ex05', 'ex04')}.service.log"
7
+ Email.client.transport = t =
8
+ ASIR::Transport::File.new(:file => service_log)
9
+ t.encoder =
10
+ ASIR::Coder::Yaml.new
11
+ t.serve_file!
12
+ end
13
+
14
+ # !SLIDE END
15
+ # EXPECT: : client process
16
+ # EXPECT: : Email.send_mail :pdf_invoice
@@ -0,0 +1,26 @@
1
+ # !SLIDE :capture_code_output true
2
+ # One-way, named pipe service
3
+
4
+ require 'example_helper'
5
+ begin
6
+ File.unlink(service_pipe = "service.pipe") rescue nil
7
+ Email.client.transport = t =
8
+ ASIR::Transport::File.new(:file => service_pipe)
9
+ t.encoder =
10
+ ASIR::Coder::Yaml.new
11
+ t.prepare_pipe_server!
12
+ server_process do
13
+ t.run_pipe_server!
14
+ end
15
+ sleep 1
16
+ pr Email.client.send_email(:pdf_invoice, :to => "user@email.com", :customer => @customer)
17
+ ensure
18
+ t.close; sleep 1; server_kill
19
+ end
20
+
21
+ # !SLIDE END
22
+ # EXPECT: : client process
23
+ # EXPECT: : server process
24
+ # EXPECT: : Email.send_mail :pdf_invoice
25
+ # EXPECT: : pr: nil
26
+
@@ -0,0 +1,28 @@
1
+ # !SLIDE :capture_code_output true
2
+ # One-way, named pipe service with signature
3
+
4
+ require 'example_helper'
5
+ begin
6
+ File.unlink(service_pipe = "service.pipe") rescue nil
7
+ Email.client.transport = t =
8
+ ASIR::Transport::File.new(:file => service_pipe)
9
+ t.encoder =
10
+ ASIR::Coder::Chain.new(:encoders =>
11
+ [ ASIR::Coder::Marshal.new,
12
+ s = ASIR::Coder::Sign.new(:secret => 'abc123'),
13
+ ASIR::Coder::Yaml.new,
14
+ ])
15
+ t.prepare_pipe_server!
16
+ server_process do
17
+ t.run_pipe_server!
18
+ end
19
+ pr Email.client.send_email(:pdf_invoice, :to => "user@email.com", :customer => @customer)
20
+ ensure
21
+ t.close; sleep 1; server_kill
22
+ end
23
+
24
+ # !SLIDE END
25
+ # EXPECT: : client process
26
+ # EXPECT: : server process
27
+ # EXPECT: : Email.send_mail :pdf_invoice
28
+ # EXPECT: : pr: nil
@@ -0,0 +1,30 @@
1
+ # !SLIDE :capture_code_output true
2
+ # One-way, named pipe service with invalid signature
3
+
4
+ require 'example_helper'
5
+ begin
6
+ File.unlink(service_pipe = "service.pipe") rescue nil
7
+ Email.client.transport = t =
8
+ ASIR::Transport::File.new(:file => service_pipe)
9
+ t.encoder =
10
+ ASIR::Coder::Chain.new(:encoders =>
11
+ [ ASIR::Coder::Marshal.new,
12
+ s = ASIR::Coder::Sign.new(:secret => 'abc123'),
13
+ ASIR::Coder::Yaml.new,
14
+ ])
15
+ t.prepare_pipe_server!
16
+ server_process do
17
+ t.run_pipe_server!
18
+ end
19
+ s.secret = 'I do not know the secret! :('
20
+ pr Email.client.send_email(:pdf_invoice, :to => "user@email.com", :customer => @customer)
21
+ ensure
22
+ t.close; sleep 1; server_kill
23
+ end
24
+
25
+ # !SLIDE END
26
+ # EXPECT: : client process
27
+ # EXPECT: : server process
28
+ # EXPECT!: : Email.send_mail :pdf_invoice
29
+ # EXPECT: : pr: nil
30
+
@@ -0,0 +1,25 @@
1
+ # !SLIDE :capture_code_output true
2
+ # Socket service
3
+
4
+ require 'example_helper'
5
+ begin
6
+ Email.client.transport = t =
7
+ ASIR::Transport::TcpSocket.new(:port => 30909)
8
+ t.encoder =
9
+ ASIR::Coder::Marshal.new
10
+ t.prepare_server!
11
+ server_process do
12
+ t.run_server!
13
+ end
14
+ pr Email.client.send_email(:pdf_invoice,
15
+ :to => "user@email.com", :customer => @customer)
16
+ ensure
17
+ t.close; sleep 1; server_kill
18
+ end
19
+
20
+ # !SLIDE END
21
+ # EXPECT: : client process
22
+ # EXPECT: : server process
23
+ # EXPECT: : Email.send_mail :pdf_invoice
24
+ # EXPECT: : pr: :ok
25
+
@@ -0,0 +1,24 @@
1
+ # !SLIDE :capture_code_output true
2
+ # Socket service with forwarded exception.
3
+
4
+ require 'example_helper'
5
+ begin
6
+ Email.client.transport = t =
7
+ ASIR::Transport::TcpSocket.new(:port => 30910)
8
+ t.encoder =
9
+ ASIR::Coder::Marshal.new
10
+ t.prepare_server!
11
+ server_process do
12
+ t.run_server!
13
+ end
14
+ pr Email.client.do_raise("Raise Me!")
15
+ rescue Exception => err
16
+ pr [ :exception, err ]
17
+ ensure
18
+ t.close; sleep 1; server_kill
19
+ end
20
+
21
+ # !SLIDE END
22
+ # EXPECT: : client process
23
+ # EXPECT: : server process
24
+ # EXPECT: : pr: [:exception, #<RuntimeError: Raise Me!>]
@@ -0,0 +1,48 @@
1
+ # !SLIDE :capture_code_output true
2
+ # Socket service with local fallback.
3
+
4
+ require 'example_helper'
5
+ begin
6
+ File.unlink(service_log = "#{__FILE__}.service.log") rescue nil
7
+ Email.client.transport = t =
8
+ ASIR::Transport::Fallback.new(:transports => [
9
+ tcp = ASIR::Transport::TcpSocket.new(:port => 31911,
10
+ :encoder => ASIR::Coder::Marshal.new),
11
+ ASIR::Transport::Broadcast.new(:transports => [
12
+ file = ASIR::Transport::File.new(:file => service_log,
13
+ :encoder => ASIR::Coder::Yaml.new),
14
+ ASIR::Transport::Subprocess.new,
15
+ ]),
16
+ ])
17
+ pr Email.client.send_email(:pdf_invoice,
18
+ :to => "user@email.com", :customer => @customer)
19
+ server_process do
20
+ tcp.prepare_server!
21
+ tcp.run_server!
22
+ end; sleep 2
23
+ pr Email.client.send_email(:pdf_invoice,
24
+ :to => "user2@email.com", :customer => @customer)
25
+ ensure
26
+ file.close rescue nil;
27
+ tcp.close rescue nil; sleep 1; server_kill
28
+ puts "\x1a\n#{service_log.inspect} contents:"
29
+ puts File.read(service_log)
30
+ end
31
+
32
+ # !SLIDE END
33
+ # EXPECT: : client process
34
+ # EXPECT: : server process
35
+ # EXPECT/: : Email.send_mail :pdf_invoice .*:to=>"user@email.com"
36
+ # EXPECT/: : Email.send_mail :pdf_invoice .*:to=>"user2@email.com"
37
+ # EXPECT: : pr: :ok
38
+ # EXPECT: service.log" contents:
39
+ # EXPECT: --- !ruby/object:ASIR::Message
40
+ # EXPECT: :transport_exceptions:
41
+ # EXPECT: ASIR::Error: Cannot connect to ASIR::Transport::TcpSocket tcp://127.0.0.1:
42
+ # EXPECT: arguments:
43
+ # EXPECT: - :pdf_invoice
44
+ # EXPECT/: :to: user@email.com
45
+ # EXPECT/: :customer: 123
46
+ # EXPECT: receiver: Email
47
+ # EXPECT: receiver_class: Module
48
+ # EXPECT: selector: :send_email
@@ -0,0 +1,34 @@
1
+ # !SLIDE :capture_code_output true
2
+ # Asynchronous beanstalkd service
3
+
4
+ require 'example_helper'
5
+ require 'asir/transport/beanstalk'
6
+ require 'asir/coder/zlib'
7
+ begin
8
+ Email.client.transport = t =
9
+ ASIR::Transport::Beanstalk.new(:address => '127.0.0.1', :port => 30904)
10
+ t.encoder =
11
+ ASIR::Coder::Chain.new(:encoders =>
12
+ [ ASIR::Coder::Marshal.new,
13
+ ASIR::Coder::Zlib.new, ])
14
+ t.start_beanstalkd!; sleep 1
15
+ pr Email.client.send_email(:pdf_invoice,
16
+ :to => "user@email.com", :customer => @customer)
17
+ sleep 2
18
+ server_process do
19
+ t.prepare_beanstalk_server!
20
+ t.run_beanstalk_server!
21
+ end
22
+ rescue Object => err
23
+ $stderr.puts "#{err.inspect}\n#{err.backtrace * "\n"}"
24
+ ensure
25
+ t.close; sleep 3; server_kill; sleep 2
26
+ t.stop_beanstalkd!
27
+ end
28
+
29
+ # !SLIDE END
30
+ # EXPECT: : client process
31
+ # EXPECT: : server process
32
+ # EXPECT: : Email.send_mail :pdf_invoice
33
+ # EXPECT: : pr: nil
34
+
@@ -0,0 +1,35 @@
1
+ # !SLIDE :capture_code_output true
2
+ # Synchronous HTTP service
3
+
4
+ require 'example_helper'
5
+ require 'asir/transport/webrick'
6
+ require 'asir/coder/base64'
7
+ require 'asir/coder/zlib'
8
+ begin
9
+ Email.client.transport = t =
10
+ ASIR::Transport::Webrick.new(:uri => "http://localhost:31913/")
11
+ t.encoder =
12
+ ASIR::Coder::Chain.new(:encoders =>
13
+ [ASIR::Coder::Marshal.new,
14
+ ASIR::Coder::Base64.new, ])
15
+ server_process do
16
+ t.prepare_server!
17
+ t.run_server!
18
+ end; sleep 2
19
+ pr Email.client.send_email(:pdf_invoice,
20
+ :to => "user@email.com",
21
+ :customer => @customer)
22
+ sleep 2
23
+ rescue Object => err
24
+ $stderr.puts "#{err.inspect}\n#{err.backtrace * "\n"}"
25
+ ensure
26
+ t.close rescue nil; sleep 3
27
+ server_kill; sleep 2
28
+ end
29
+
30
+ # !SLIDE END
31
+ # EXPECT: : client process
32
+ # EXPECT: : server process
33
+ # EXPECT: : Email.send_mail :pdf_invoice
34
+ # EXPECT: : pr: :ok
35
+