rubytest 0.4.2 → 0.5.0

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.
data/.ruby CHANGED
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  source:
3
3
  - var
4
- - var/
5
4
  authors:
6
5
  - name: trans
7
6
  email: transfire@gmail.com
@@ -37,7 +36,7 @@ revision: 0
37
36
  created: '2011-07-23'
38
37
  summary: Ruby Universal Test Harness
39
38
  title: Ruby Test
40
- version: 0.4.2
39
+ version: 0.5.0
41
40
  name: rubytest
42
41
  description: ! "Ruby Test is a universal test harness for Ruby. It can handle any
43
42
  compliant \ntest framework, even running tests from multiple frameworks in a single
data/HISTORY.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # RELEASE HISTORY
2
2
 
3
+ ## 0.5.0 / 2012-03-22
4
+
5
+ This release improves the command line interface, the handling of
6
+ configuration and provides a mechinism for adding custom test
7
+ observers. The later can be used for things like mock library
8
+ verifcation steps.
9
+
10
+ Changes:
11
+
12
+ * Make CLI an actual class.
13
+ * Refactor configuration file lookup and support Confection.
14
+ * Add customizable test observer via new Advice class.
15
+
16
+
3
17
  ## 0.4.2 / 2012-03-04
4
18
 
5
19
  Minor release to fix detection of pre-existance of Exception core
File without changes
data/bin/ruby-test CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'rubytest'
3
- Test::Runner.cli(*ARGV)
3
+ Test::CLI.run(*ARGV)
4
4
 
data/bin/rubytest CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env ruby
2
2
  require 'rubytest'
3
- Test::Runner.cli(*ARGV)
3
+ Test::CLI.run(*ARGV)
4
4
 
@@ -0,0 +1,134 @@
1
+ module Test
2
+
3
+ # The Advice class is an observer that can be customized to
4
+ # initiate before, after and upon procedures for all of RubyTests
5
+ # observable points.
6
+ #
7
+ # Only one procedure is allowed per-point.
8
+ #
9
+ class Advice
10
+
11
+ #
12
+ def self.joinpoints
13
+ @joinpoints ||= []
14
+ end
15
+
16
+ # TODO: Should before and affter hooks be evaluated in the context of test
17
+ # object scope? The #scope field has been added to the RubyTest spec
18
+ # just in case.
19
+
20
+ #
21
+ def self.joinpoint(name)
22
+ joinpoints << name.to_sym
23
+
24
+ class_eval %{
25
+ def #{name}(*args)
26
+ procedure = @table[:#{name}]
27
+ procedure.call(*args) if procedure
28
+ end
29
+ }
30
+ end
31
+
32
+ #
33
+ def initialize
34
+ @table = {}
35
+ end
36
+
37
+ # @method begin_suite(suite)
38
+ joinpoint :begin_suite
39
+
40
+ # @method begin_case(case)
41
+ joinpoint :begin_case
42
+
43
+ # @method skip_test(test, reason)
44
+ joinpoint :skip_test
45
+
46
+ # @method begin_test(test)
47
+ joinpoint :begin_test
48
+
49
+ # @method pass(test)
50
+ joinpoint :pass
51
+
52
+ # @method fail(test, exception)
53
+ joinpoint :fail
54
+
55
+ # @method error(test, exception)
56
+ joinpoint :error
57
+
58
+ # @method todo(test, exception)
59
+ joinpoint :todo
60
+
61
+ # @method end_test(test)
62
+ joinpoint :end_test
63
+
64
+ # @method end_case(case)
65
+ joinpoint :end_case
66
+
67
+ # @method end_suite(suite)
68
+ joinpoint :end_suite
69
+
70
+ #
71
+ #def [](key)
72
+ # @table[key.to_sym]
73
+ #end
74
+
75
+ # Add a procedure to one of the join-points.
76
+ def join(type, &block)
77
+ type = valid_type(type)
78
+ @table[type] = block
79
+ end
80
+
81
+ # Add a procedure to one of the before join-points.
82
+ def join_before(type, &block)
83
+ join("begin_#{type}", &block)
84
+ end
85
+
86
+ # Add a procedure to one of the after join-points.
87
+ def join_after(type, &block)
88
+ join("end_#{type}", &block)
89
+ end
90
+
91
+ # Ignore any other signals (precautionary).
92
+ def method_missing(*)
93
+ end
94
+
95
+ private
96
+
97
+ #def invoke(symbol, *args)
98
+ # if @table.key?(symbol)
99
+ # self[symbol].call(*args)
100
+ # end
101
+ #end
102
+
103
+ #
104
+ def valid_type(type)
105
+ type = message_type(type)
106
+ unless self.class.joinpoints.include?(type)
107
+ raise ArgumentError, "not a valid advice type -- #{type}"
108
+ end
109
+ type
110
+ end
111
+
112
+ #
113
+ def message_type(type)
114
+ type = type.to_sym
115
+ case type
116
+ when :each
117
+ type = :test
118
+ when :all
119
+ type = :case
120
+ when :begin_each
121
+ type = :begin_test
122
+ when :begin_all
123
+ type = :begin_case
124
+ when :end_each
125
+ type = :end_test
126
+ when :end_all
127
+ type = :end_case
128
+ end
129
+ return type
130
+ end
131
+
132
+ end
133
+
134
+ end
data/lib/rubytest/cli.rb CHANGED
@@ -1,16 +1,51 @@
1
1
  module Test
2
2
 
3
- # Command line interface.
4
- class Runner
3
+ # Command line interface to test runner.
4
+ #
5
+ class CLI
5
6
 
6
- # Test runner command line interface.
7
7
  #
8
- def self.cli(*argv)
9
- runner = Runner.new
8
+ # Convenience method for invoking the CLI.
9
+ #
10
+ def self.run(*argv)
11
+ new.run(*argv)
12
+ end
13
+
14
+ #
15
+ # Initialize CLI instance.
16
+ #
17
+ def initialize
18
+ require 'optparse'
10
19
 
11
20
  Test::Config.load
12
21
 
13
- cli_options(runner, argv)
22
+ @profile = 'default'
23
+ @config = Test.config
24
+ @runner = Runner.new
25
+ end
26
+
27
+ #
28
+ # @return [Runner]
29
+ #
30
+ attr :runner
31
+
32
+ #
33
+ # @return [Hash]
34
+ #
35
+ attr :config
36
+
37
+ #
38
+ attr_accessor :profile
39
+
40
+ #
41
+ # Run tests.
42
+ #
43
+ def run(*argv)
44
+ options.parse!(argv)
45
+
46
+ run_profile
47
+
48
+ runner.files.replace(argv) unless argv.empty?
14
49
 
15
50
  Test::Config.load_path_setup #unless runner.autopath == false
16
51
 
@@ -24,31 +59,43 @@ module Test
24
59
  end
25
60
 
26
61
  #
27
- def self.cli_options(runner, argv)
28
- require 'optparse'
62
+ # Run the common profile if defined and then the specific
63
+ # profile.
64
+ #
65
+ def run_profile
66
+ raise "no such profile -- #{profile}" unless config[profile] or profile == 'default'
29
67
 
30
- config = Test.config.dup
31
- config_loaded = false
68
+ common = config['common']
69
+ common.call(runner) if common
32
70
 
33
- common = config.delete('common')
34
- default = config.delete('default')
71
+ profig = config[profile]
72
+ profig.call(runner) if profig
73
+ end
35
74
 
36
- common.call(runner) if common
75
+ #
76
+ # Setup OptionsParser instance.
77
+ #
78
+ def options
79
+ this = self
37
80
 
38
81
  OptionParser.new do |opt|
39
82
  opt.banner = "Usage: #{$0} [options] [files ...]"
40
83
 
41
- unless config.empty?
42
- opt.separator "PRESET OPTIONS:"
43
- config.each do |name, block|
44
- opt.on("--#{name}") do
45
- block.call(runner)
46
- end
84
+ opt.separator "PRESET OPTIONS:"
85
+
86
+ opt.on '-p', '--profile NAME', "use configuration profile" do |name|
87
+ this.profile = name.to_s
88
+ end
89
+
90
+ config_names = config.keys - ['common', 'default']
91
+
92
+ unless config_names.empty?
93
+ config_names.each do |name|
94
+ opt.separator((" " * 40) + "* #{name}")
47
95
  end
48
96
  end
49
97
 
50
98
  opt.separator "CONFIG OPTIONS:"
51
-
52
99
  opt.on '-f', '--format NAME', 'report format' do |name|
53
100
  runner.format = name
54
101
  end
@@ -83,22 +130,20 @@ module Test
83
130
  #opt.on('--log DIRECTORY', 'log directory'){ |dir|
84
131
  # options[:log] = dir
85
132
  #}
86
- opt.on_tail("--[no-]ansi" , 'turn on/off ANSI colors'){ |v| $ansi = v }
87
- opt.on_tail("--debug" , 'turn on debugging mode'){ $DEBUG = true }
88
- #opt.on_tail("--about" , 'display information about lemon'){
133
+ opt.on("--[no-]ansi" , 'turn on/off ANSI colors'){ |v| $ansi = v }
134
+ opt.on("--debug" , 'turn on debugging mode'){ $DEBUG = true }
135
+
136
+ opt.separator "COMMAND OPTIONS:"
137
+ #opt.on("--about" , 'display information about rubytest'){
89
138
  # puts "Ruby Test v#{VERSION}"
90
139
  # puts "#{COPYRIGHT}"
91
140
  # exit
92
141
  #}
93
- opt.on_tail('-h', '--help', 'display this help message'){
142
+ opt.on('-h', '--help', 'display this help message'){
94
143
  puts opt
95
144
  exit
96
145
  }
97
- end.parse!(argv)
98
-
99
- default.call(runner) if default && !config_loaded
100
-
101
- runner.files.replace(argv) unless argv.empty?
146
+ end
102
147
  end
103
148
 
104
149
  end
@@ -1,57 +1,104 @@
1
1
  module Test
2
2
 
3
3
  #
4
- def self.run(name=:default, &block)
4
+ def self.run(name=nil, &block)
5
+ name = name ? name : 'default'
6
+
5
7
  @config ||= {}
6
8
  @config[name.to_s] = block
7
9
  end
8
10
 
11
+ #
9
12
  def self.config
10
13
  @config ||= {}
11
14
  end
12
15
 
16
+ # Handle test run configruation.
17
+ #
18
+ # @todo Why not use the instace level for `Test.config` ?
13
19
  #
14
20
  class Config
15
21
 
16
- # Test configuration file.
22
+ # Tradional test configuration file glob. This glob
23
+ # looks for a `Testfile` or a `.test` file.
24
+ # Either can have an optional `.rb` extension.
25
+ GLOB_RC = '{testfile.rb,testfile,.test.rb,.test}'
26
+
27
+ # If a root level test file can't be found, then use this
28
+ # glob to search the task directory for `*.rubytest` files.
29
+ GLOB_TASK = 'task/*.rubytest'
30
+
31
+ # Glob used to find project root directory.
32
+ GLOB_ROOT = '{.ruby,.git,.hg,_darcs,lib/}'
33
+
17
34
  #
18
- # The name of the file is an ode to the original Ruby cli test tool.
35
+ # Load configuration. This will first look for a root level `Testfile.rb`
36
+ # or `.test.rb` file. Failing that it will look for `task/*.rubytest` files.
37
+ # An example entry into any of these look like:
19
38
  #
20
- # @example
21
- # .test
22
- # .testrb
23
- # .test.rb
24
- # .config/test.rb
39
+ # Test.run :name do |run|
40
+ # run.files << 'test/case_*.rb'
41
+ # end
25
42
  #
26
- # @todo Too many options for ruby-test configuration file.
27
- GLOB_RC = '{.testrb,.test.rb,.test,.config/test.rb,config/test.rb}'
28
-
43
+ # Use `:default` for name for non-specific profile and `:common` for code that
44
+ # should apply to all configurations.
29
45
  #
30
- GLOB_ROOT = '{.ruby,.git,.hg}'
31
-
46
+ # Failing any traditional configuration files, the Confection system will
47
+ # be used. An example entry in a projects `Confile` is:
48
+ #
49
+ # config :test, :name do |run|
50
+ # run.files << 'test/case_*.rb'
51
+ # end
52
+ #
53
+ # You can leave the `:name` parameter out for `:default`.
32
54
  #
33
55
  def self.load
34
- super(rc_file) if rc_file
35
- #Ruth.module_eval(File.read(rc_file)) if rc_file
56
+ if rc_files.empty?
57
+ if confection_file
58
+ require 'confection'
59
+ confection('test', '*').each do |c|
60
+ name = c.profile ? c.profile : :default
61
+ Test.run(name, &c)
62
+ end
63
+ end
64
+ else
65
+ rc_files.each do |file|
66
+ super(file)
67
+ end
68
+ end
69
+ end
70
+
71
+ #
72
+ def self.confection_file
73
+ @confection_file ||= (
74
+ Dir.glob(File.join(root, '{,.}confile{,.rb}'), File::FNM_CASEFOLD).first
75
+ )
36
76
  end
37
77
 
38
78
  # Find rc file.
39
- def self.rc_file
40
- @rc_file ||= (
41
- glob = GLOB_RC
42
- stop = root
43
- default = nil
44
- dir = Dir.pwd
45
- file = nil
46
- loop do
47
- file = Dir[File.join(dir, glob)].first
48
- break file if file
49
- break if dir == stop
50
- dir = File.dirname(dir)
51
- break if dir == '/'
79
+ def self.rc_files
80
+ @rc_files ||= (
81
+ if file = Dir.glob(File.join(root, GLOB_RC), File::FNM_CASEFOLD).first
82
+ [file]
83
+ else
84
+ Dir.glob(File.join(root, GLOB_TASK))
52
85
  end
53
- file ? file : default
54
86
  )
87
+
88
+ # glob = GLOB_RC
89
+ # stop = root
90
+ # default = nil
91
+ # dir = Dir.pwd
92
+ # file = nil
93
+ # loop do
94
+ # files = Dir.glob(File.join(dir, glob), File::FNM_CASEFOLD)
95
+ # file = files.find{ |f| File.file?(f) }
96
+ # break file if file
97
+ # break if dir == stop
98
+ # dir = File.dirname(dir)
99
+ # break if dir == '/'
100
+ # end
101
+ # file ? file : default
55
102
  end
56
103
 
57
104
  # Find and cache project root directory.
@@ -14,8 +14,8 @@ module Test
14
14
  end
15
15
 
16
16
  #
17
- def skip(test)
18
- self[:skip] << test
17
+ def skip_test(test, reason)
18
+ self[:skip] << [test, reason]
19
19
  end
20
20
 
21
21
  # Add `test` to pass set.
@@ -35,9 +35,9 @@ module Test
35
35
  self[:todo] << [test, exception]
36
36
  end
37
37
 
38
- def omit(test, exception)
39
- self[:omit] << [test, exception]
40
- end
38
+ #def omit(test, exception)
39
+ # self[:omit] << [test, exception]
40
+ #end
41
41
 
42
42
  # Returns true if their are no test errors or failures.
43
43
  def success?
@@ -63,7 +63,7 @@ module Test
63
63
  def fail(test, exception)
64
64
  end
65
65
 
66
- #
66
+ # Report a test error.
67
67
  def error(test, exception)
68
68
  end
69
69
 
@@ -71,10 +71,6 @@ module Test
71
71
  def todo(test, exception)
72
72
  end
73
73
 
74
- # Report an omitted test.
75
- def omit(test, exception)
76
- end
77
-
78
74
  #
79
75
  def end_test(test)
80
76
  end
@@ -5,6 +5,10 @@ module Test::Reporters
5
5
  # Simple Dot-Progress Reporter
6
6
  class Dotprogress < Abstract
7
7
 
8
+ def skip_test(unit, reason)
9
+ print "S".ansi(:cyan) if runner.verbose?
10
+ end
11
+
8
12
  def pass(unit)
9
13
  print "."
10
14
  $stdout.flush
@@ -25,11 +29,6 @@ module Test::Reporters
25
29
  $stdout.flush
26
30
  end
27
31
 
28
- def omit(unit, exception)
29
- print "O".ansi(:cyan)
30
- $stdout.flush
31
- end
32
-
33
32
  def end_suite(suite)
34
33
  puts; puts
35
34
  puts timestamp
@@ -37,12 +36,10 @@ module Test::Reporters
37
36
 
38
37
  if runner.verbose?
39
38
  unless record[:omit].empty?
40
- puts "OMISSIONS\n\n"
41
- record[:omit].each do |test, exception|
39
+ puts "SKIPPED\n\n"
40
+ record[:skip].each do |test, reason|
42
41
  puts " #{test}".ansi(:bold)
43
- puts " #{exception}"
44
- puts " #{file_and_line(exception)}"
45
- #puts code(exception)
42
+ puts " #{reason}" if String===reason
46
43
  puts
47
44
  end
48
45
  end
@@ -113,6 +113,29 @@ module Test
113
113
  @hard = !!boolean
114
114
  end
115
115
 
116
+ # Instance of Advice is a special customizable observer.
117
+ def advice
118
+ @advice
119
+ end
120
+
121
+ # Define universal before advice.
122
+ def before(type, &block)
123
+ advice.join_before(type, &block)
124
+ end
125
+
126
+ # Define universal after advice. Can be used by mock libraries,
127
+ # for example to run mock verification.
128
+ def after(type, &block)
129
+ advice.join_after(type, &block)
130
+ end
131
+
132
+ # Define universal upon advice.
133
+ #
134
+ # See {Advice} for valid join-points.
135
+ def upon(type, &block)
136
+ advice.join(type, &block)
137
+ end
138
+
116
139
  # New Runner.
117
140
  #
118
141
  # @param [Array] suite
@@ -128,13 +151,15 @@ module Test
128
151
  @verbose = options[:verbose] || self.class.verbose
129
152
  @hard = options[:hard] || self.class.hard
130
153
 
154
+ @advice = Advice.new
155
+
131
156
  block.call(self) if block
132
157
  end
133
158
 
134
159
  # The reporter to use for ouput.
135
160
  attr :reporter
136
161
 
137
- # Record pass, fail, error, pending and omitted tests.
162
+ # Record pass, fail, error and pending tests.
138
163
  attr :recorder
139
164
 
140
165
  # Array of observers, typically this just contains the recorder and
@@ -155,7 +180,8 @@ module Test
155
180
 
156
181
  @reporter = reporter_load(format)
157
182
  @recorder = Recorder.new
158
- @observers = [@reporter, @recorder]
183
+
184
+ @observers = [advice, @recorder, @reporter]
159
185
 
160
186
  #cd_tmp do
161
187
  observers.each{ |o| o.begin_suite(suite) }
@@ -196,8 +222,8 @@ module Test
196
222
  # Run a test case.
197
223
  #
198
224
  def run_case(tcase)
199
- if tcase.respond_to?(:skip?) && tcase.skip?
200
- return observers.each{ |o| o.skip_case(tcase) }
225
+ if tcase.respond_to?(:skip?) && (reason = tcase.skip?)
226
+ return observers.each{ |o| o.skip_case(tcase, reason) }
201
227
  end
202
228
 
203
229
  observers.each{ |o| o.begin_case(tcase) }
@@ -219,8 +245,8 @@ module Test
219
245
  # The test to run, must repsond to #call.
220
246
  #
221
247
  def run_test(test)
222
- if test.respond_to?(:skip?) && test.skip?
223
- return observers.each{ |o| o.skip_test(test) }
248
+ if test.respond_to?(:skip?) && (reason = test.skip?)
249
+ return observers.each{ |o| o.skip_test(test, reason) }
224
250
  end
225
251
 
226
252
  observers.each{ |o| o.begin_test(test) }
data/lib/rubytest.rb CHANGED
@@ -11,6 +11,7 @@ if RUBY_VERSION < '1.9'
11
11
  require 'rubytest/code_snippet'
12
12
  require 'rubytest/config'
13
13
  require 'rubytest/recorder'
14
+ require 'rubytest/advice'
14
15
  require 'rubytest/runner'
15
16
  require 'rubytest/cli'
16
17
  require 'rubytest/reporters/abstract'
@@ -18,8 +19,9 @@ if RUBY_VERSION < '1.9'
18
19
  else
19
20
  require_relative 'rubytest/core_ext'
20
21
  require_relative 'rubytest/code_snippet'
21
- require_relative 'rubytest/recorder'
22
22
  require_relative 'rubytest/config'
23
+ require_relative 'rubytest/recorder'
24
+ require_relative 'rubytest/advice'
23
25
  require_relative 'rubytest/runner'
24
26
  require_relative 'rubytest/cli'
25
27
  require_relative 'rubytest/reporters/abstract'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubytest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.2
4
+ version: 0.5.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-04 00:00:00.000000000 Z
12
+ date: 2012-03-22 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ansi
16
- requirement: &20859580 !ruby/object:Gem::Requirement
16
+ requirement: &14620080 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *20859580
24
+ version_requirements: *14620080
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: detroit
27
- requirement: &20856920 !ruby/object:Gem::Requirement
27
+ requirement: &14639980 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *20856920
35
+ version_requirements: *14639980
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: qed
38
- requirement: &20879280 !ruby/object:Gem::Requirement
38
+ requirement: &14638520 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,7 +43,7 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *20879280
46
+ version_requirements: *14638520
47
47
  description: ! "Ruby Test is a universal test harness for Ruby. It can handle any
48
48
  compliant \ntest framework, even running tests from multiple frameworks in a single
49
49
  pass."
@@ -54,18 +54,18 @@ executables:
54
54
  - rubytest
55
55
  extensions: []
56
56
  extra_rdoc_files:
57
+ - LICENSE.txt
57
58
  - HISTORY.md
58
59
  - README.md
59
- - NOTICE.md
60
60
  files:
61
61
  - .ruby
62
- - .test
63
62
  - bin/ruby-test
64
63
  - bin/rubytest
65
64
  - demo/01_test.md
66
65
  - demo/02_case.md
67
66
  - demo/applique/ae.rb
68
67
  - demo/applique/rubytest.rb
68
+ - lib/rubytest/advice.rb
69
69
  - lib/rubytest/autorun.rb
70
70
  - lib/rubytest/cli.rb
71
71
  - lib/rubytest/code_snippet.rb
@@ -93,9 +93,9 @@ files:
93
93
  - lib/test.rb
94
94
  - test/basic_case.rb
95
95
  - Gemfile.lock
96
+ - LICENSE.txt
96
97
  - HISTORY.md
97
98
  - README.md
98
- - NOTICE.md
99
99
  homepage: http://rubyworks.github.com/rubytest
100
100
  licenses:
101
101
  - BSD-2-Clause
data/.test DELETED
@@ -1,11 +0,0 @@
1
- Test.run(:default) do |run|
2
- run.files << 'test/*_case.rb'
3
- end
4
-
5
- Test.run(:cov) do |run|
6
- run.files << 'test/*_case.rb'
7
- SimpleCov.start do |cov|
8
- cov.coverage_dir = 'log/coverage'
9
- end
10
- end
11
-