solutious-stella 0.7.0.003 → 0.7.0.004

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,9 @@
1
1
  = Stella - 0.7 PREVIEW
2
2
 
3
- Perform load tests on your web applications with beauty and brute strength.
3
+ <b>Perform load tests on your web applications with beauty and brute strength.</b>
4
+
5
+
4
6
 
5
- <i>NOTE: 0.7 release is not compatible with previous releases!</i>
6
7
 
7
8
  == Features
8
9
 
@@ -10,13 +11,14 @@ Perform load tests on your web applications with beauty and brute strength.
10
11
  * Sophisticated response handling (with automatic HTML document parsing)
11
12
  * Dynamic variable replacement
12
13
 
14
+
13
15
  == PREVIEW NOTICE
14
16
 
15
- This is an early preview of Stella. It's still missing the following features (as of 2009-09-15):
17
+ This is an early preview of Stella. The following features are still under development (as of 2009-09-15):
16
18
 
17
19
  * Reporting of any kind.
18
- * Documentation.
19
20
  * File uploads.
21
+ * Documentation.
20
22
 
21
23
  == Usage Example
22
24
 
@@ -28,9 +30,6 @@ This is an early preview of Stella. It's still missing the following features (a
28
30
  # using the same test plan.
29
31
  $ stella load -p examples/basic/plan.rb http://stellaaahhhh.com/
30
32
 
31
- # Preview a test plan
32
- $ stella preview -p examples/basic/plan.rb
33
-
34
33
 
35
34
  == Test Plan Example
36
35
 
@@ -105,12 +104,14 @@ Get it in one of the following ways:
105
104
  * Delano Mandelbaum (http://solutious.com)
106
105
  * Threadify (C) Ara T Howard (http://codeforpeople.com/)
107
106
 
107
+
108
108
  == Thanks
109
109
 
110
110
  * Harm Aarts for the great test case and feedback!
111
111
  * Kalin Harvey for keeping me on track.
112
112
  * Dave L, the best intern money can't buy.
113
113
 
114
+
114
115
  == License
115
116
 
116
117
  See LICENSE.txt
data/bin/stella CHANGED
@@ -81,14 +81,6 @@ class Stella::CLI::Definition
81
81
  end
82
82
  command :load => Stella::CLI
83
83
 
84
- about "Preview a testplan"
85
- usage "stella preview -p path/2/testplan.rb http://stellaaahhhh.com/"
86
- option :p, :testplan, String, "Path to testplan" do |v|
87
- raise Stella::InvalidOption, "Bad path: #{v}" unless File.exists?(v)
88
- v
89
- end
90
- command :preview => Stella::CLI
91
-
92
84
  about "Initialize Stella"
93
85
  command :init do
94
86
  Stella::Config.init
@@ -117,6 +109,7 @@ class Stella::CLI::Definition
117
109
  puts
118
110
  puts "Elapsed: %.2f seconds" % @elapsed.to_f
119
111
  end
112
+ exit obj.exit_code || 0
120
113
  end
121
114
 
122
115
  end
@@ -3,6 +3,7 @@
3
3
  desc "Business Finder Testplan"
4
4
 
5
5
  usecase 65, "Simple search" do
6
+ resource :search_terms, list('search_terms.csv')
6
7
 
7
8
  get "/", "Homepage" do
8
9
  wait 1..5
@@ -16,8 +17,8 @@ usecase 65, "Simple search" do
16
17
 
17
18
  get "/search", "Search Results" do
18
19
  wait 2..5
19
- param :what => 'Big'
20
- param :where => ''
20
+ param :what => random(:search_terms)
21
+ param :where => random(['Toronto', 'Montreal'])
21
22
  response 200 do
22
23
  listing = doc.css('div.listing').first
23
24
  set :lid, listing['id'].match(/(\d+)/)[0]
@@ -31,15 +32,18 @@ usecase 65, "Simple search" do
31
32
 
32
33
  end
33
34
 
34
- usecase "YAML API" do
35
- resource :preset_listing_ids, list('listing_ids.csv')
36
-
37
- get "/listing/:lid.yaml", "Select listing" do
38
- param :lid => random(:preset_listing_ids)
39
- response 200 do
40
- repeat 5
35
+ usecase 10, "Self-serve" do
36
+ post "/listing/add", "Add a listing" do
37
+ wait 1..4
38
+ param :name => random(8)
39
+ param :city => "Vancouver"
40
+ response 302 do
41
+ repeat 3
41
42
  end
42
43
  end
44
+ end
45
+
46
+ usecase "Listing API" do
43
47
 
44
48
  get '/listings.yaml', "View All" do
45
49
  response 200 do
@@ -49,7 +53,7 @@ usecase "YAML API" do
49
53
  end
50
54
  end
51
55
 
52
- get "/listing/:lid.yaml", "Select listing" do
56
+ get "/listing/:lid.yaml", "Select (sequential)" do
53
57
  param :lid => rsequential(:current_listing_ids)
54
58
  response 200 do
55
59
  repeat 7
@@ -58,14 +62,3 @@ usecase "YAML API" do
58
62
 
59
63
  end
60
64
 
61
- usecase 10, "Self-serve" do
62
- post "/listing/add" do
63
- desc "Add a business"
64
- wait 1..4
65
- param :name => random(8)
66
- param :city => "Vancouver"
67
- response 302 do
68
- repeat 3
69
- end
70
- end
71
- end
@@ -0,0 +1,19 @@
1
+ west
2
+ smoked
3
+ pen
4
+ fire
5
+ ink
6
+ town
7
+ big
8
+ ti
9
+ al
10
+ furniture
11
+ keyboard
12
+ rods
13
+ beads
14
+ fu
15
+ w
16
+ pe
17
+ op
18
+ on
19
+ k
@@ -15,6 +15,19 @@ set :port => 3114
15
15
  set :reload => true
16
16
  set :max_listings => 1000
17
17
 
18
+ LISTINGS = [
19
+ { :id => 1000, :name => 'John West Smoked Oysters', :city => 'Toronto' },
20
+ { :id => 1001, :name => 'Fire Town Lightning Rods', :city => 'Toronto' },
21
+ { :id => 1002, :name => 'Oversized Pen and Ink Co', :city => 'Toronto' },
22
+ { :id => 1003, :name => 'The Rathzenburg Brothers', :city => 'Toronto' },
23
+ { :id => 1004, :name => 'Forever and Always Beads', :city => 'Montreal' },
24
+ { :id => 1005, :name => "Big Al's Flavour Country", :city => 'Montreal' },
25
+ { :id => 1006, :name => 'Big Time Furniture World', :city => 'Montreal' },
26
+ { :id => 1007, :name => 'High-End Keyboard Makers', :city => 'Montreal' }
27
+ ]
28
+
29
+ set :listings => LISTINGS.clone
30
+
18
31
  #log = File.new("/dev/null", "a")
19
32
  #STDOUT.reopen(log)
20
33
  #STDERR.reopen(log)
@@ -100,16 +113,6 @@ get '/listings.yaml' do
100
113
  @listings.to_yaml
101
114
  end
102
115
 
103
- set :listings => [
104
- { :id => 1000, :name => 'John West Smoked Oysters', :city => 'Toronto' },
105
- { :id => 1001, :name => 'Fire Town Lightning Rods', :city => 'Toronto' },
106
- { :id => 1002, :name => 'Oversized Pen and Ink Co', :city => 'Toronto' },
107
- { :id => 1003, :name => 'The Rathzenburg Brothers', :city => 'Toronto' },
108
- { :id => 1004, :name => 'Forever and Always Beads', :city => 'Montreal' },
109
- { :id => 1005, :name => "Big Al's Flavour Country", :city => 'Montreal' },
110
- { :id => 1006, :name => 'Big Time Furniture World', :city => 'Montreal' },
111
- { :id => 1007, :name => 'High-End Keyboard Makers', :city => 'Montreal' }
112
- ]
113
116
 
114
117
  before do
115
118
  @cookie = request.cookies["bff-history"]
@@ -118,6 +121,7 @@ before do
118
121
  if params[:clear] == 'true'
119
122
  @cookie[:history] = []
120
123
  @cookie[:location] = ''
124
+ set :listings => LISTINGS.clone
121
125
  end
122
126
  @cookie[:history].delete params[:what]
123
127
  @cookie[:history].unshift params[:what] unless blank?(params[:what])
@@ -0,0 +1,19 @@
1
+
2
+
3
+ usecase "Exception Handling" do
4
+
5
+ get "/search", "Search Results" do
6
+ wait 2..5
7
+ param :what => random()
8
+ param :where => random('Toronto', 'Montreal', 'Vancouver')
9
+ response 200 do
10
+ listing = doc.css('div.listing').first
11
+ set :lid, listing['id'].match(/(\d+)/)[0]
12
+ raise NoListingResultFound if listing.nil?
13
+ end
14
+ response 404 do
15
+ raise NoSearchResults
16
+ end
17
+ end
18
+
19
+ end
@@ -35,6 +35,7 @@ module Stella
35
35
 
36
36
  @@logger = Drydock::Screen
37
37
  @@loglev = 1
38
+ @@debug = false
38
39
 
39
40
  # Puts +msg+ to +@@logger+
40
41
  def li(*msg); msg.each { |m| @@logger.puts m } if !quiet? end
@@ -58,9 +59,9 @@ module Stella
58
59
  def enable_quiet; @@loglev = 0; end
59
60
  def disable_quiet; @@loglev = 1; end
60
61
 
61
- def debug?; @@loglev > 3; end
62
- def enable_debug; @@loglev = 4; end
63
- def disable_debug; @@loglev = 1; end
62
+ def debug?; @@debug == true; end
63
+ def enable_debug; @@debug = true; end
64
+ def disable_debug; @@debug = false; end
64
65
 
65
66
  def rescue(&blk)
66
67
  blk.call
@@ -1,9 +1,11 @@
1
1
 
2
2
 
3
3
  class Stella::CLI < Drydock::Command
4
+ attr_accessor :exit_code
4
5
 
5
6
  def init
6
7
  @conf = Stella::Config.refresh
8
+ @exit_code = 0
7
9
  end
8
10
 
9
11
  def verify_valid?
@@ -14,7 +16,8 @@ class Stella::CLI < Drydock::Command
14
16
  opts = {}
15
17
  opts[:hosts] = @hosts
16
18
  opts[:benchmark] = true if @option.benchmark
17
- Stella::Engine::Functional.run @testplan, opts
19
+ ret = Stella::Engine::Functional.run @testplan, opts
20
+ @exit_code = (ret ? 0 : 1)
18
21
  end
19
22
 
20
23
  def load_valid?
@@ -27,18 +30,9 @@ class Stella::CLI < Drydock::Command
27
30
  [:benchmark, :users, :repetitions, :delay, :time].each do |opt|
28
31
  opts[opt] = @option.send(opt) unless @option.send(opt).nil?
29
32
  end
30
- Stella::Engine::Load.run @testplan, opts
33
+ ret = Stella::Engine::Load.run @testplan, opts
34
+ @exit_code = (ret ? 0 : 1)
31
35
  end
32
-
33
- def preview_valid?
34
- create_testplan
35
- end
36
-
37
- def preview
38
- Stella.li2 "file: #{@option.testplan} (#{@testplan.digest})"
39
- Stella.li @testplan.pretty
40
- end
41
-
42
36
 
43
37
  private
44
38
  def create_testplan
@@ -46,18 +40,12 @@ class Stella::CLI < Drydock::Command
46
40
  if @option.testplan
47
41
  @testplan = Stella::Testplan.load_file @option.testplan
48
42
  else
49
- @testplan = Stella::Testplan.new
50
- usecase = Stella::Testplan::Usecase.new
51
- @argv.each do |uri|
52
- uri = URI.parse uri
53
- uri.path = '/' if uri.path.empty?
54
- req = usecase.add_request :get, uri.path
55
- req.wait = @option.delay if @option.delay
56
- end
57
- @testplan.add_usecase usecase
43
+ opts = {}
44
+ opts[:delay] = @option.delay if @option.delay
45
+ @testplan = Stella::Testplan.new(@argv, opts)
58
46
  end
59
47
  @testplan.check! # raise errors, update usecase ratios
60
- Stella.ld "PLANHASH: #{@testplan.digest}"
48
+ Stella.li3 " File: #{@option.testplan} (#{@testplan.digest})", $/
61
49
  true
62
50
  end
63
51
 
@@ -163,6 +163,17 @@ module Stella
163
163
  ret
164
164
  end
165
165
 
166
+ class ResponseError < Stella::Error
167
+ def initialize(k, m=nil)
168
+ @kind, @msg = k, m
169
+ end
170
+ def message
171
+ msg = "#{@kind}"
172
+ msg << ": #{@msg}" unless @msg.nil?
173
+ msg
174
+ end
175
+ end
176
+
166
177
  class Container
167
178
  attr_accessor :usecase
168
179
  attr_accessor :response
@@ -170,6 +181,10 @@ module Stella
170
181
  @usecase = usecase
171
182
  end
172
183
 
184
+ def self.const_missing(const, *args)
185
+ ResponseError.new(const)
186
+ end
187
+
173
188
  def doc
174
189
  # NOTE: It's important to parse the document on every
175
190
  # request because this container is available for the
@@ -1,11 +1,10 @@
1
1
 
2
2
  module Stella::Data
3
-
4
- module Helpers
5
3
 
4
+ module Helpers
6
5
 
7
- def random(input=nil)
8
-
6
+ def random(*args)
7
+ input = args.size > 1 ? args : args.first
9
8
  Proc.new do
10
9
  value = case input.class.to_s
11
10
  when "Symbol"
@@ -19,6 +18,7 @@ module Stella::Data
19
18
  when "NilClass"
20
19
  Stella::Utils.strand( rand(100) )
21
20
  end
21
+ raise Stella::Testplan::Usecase::UnknownResource, input if value.nil?
22
22
  Stella.ld "RANDVALUES: #{input} #{value.inspect}"
23
23
  value = value[ rand(value.size) ] if value.is_a?(Array)
24
24
  Stella.ld "SELECTED: #{value}"
@@ -26,7 +26,8 @@ module Stella::Data
26
26
  end
27
27
  end
28
28
 
29
- def sequential(input=nil)
29
+ def sequential(*args)
30
+ input = args.size > 1 ? args : args.first
30
31
  digest = input.gibbler
31
32
  Proc.new do
32
33
  value = case input.class.to_s
@@ -52,7 +53,8 @@ module Stella::Data
52
53
  end
53
54
  end
54
55
 
55
- def rsequential(input=nil)
56
+ def rsequential(*args)
57
+ input = args.size > 1 ? args : args.first
56
58
  digest = input.gibbler
57
59
  Proc.new do
58
60
  value = case input.class.to_s
@@ -32,8 +32,8 @@ module Stella::Engine
32
32
  container.headers.all.each do |pair|
33
33
  Stella.li3 " %s: %s" % pair
34
34
  end
35
- Stella.li3 $/, " Content:"
36
- Stella.li3 container.body.empty? ? ' [empty]' : container.body
35
+ Stella.li4 $/, " Content:"
36
+ Stella.li4 container.body.empty? ? ' [empty]' : container.body
37
37
  Stella.li2 $/
38
38
  end
39
39
 
@@ -12,11 +12,16 @@ module Stella::Engine
12
12
  }.merge! opts
13
13
  Stella.ld "OPTIONS: #{opts.inspect}"
14
14
  Stella.li2 "Hosts: " << opts[:hosts].join(', ') if !opts[:hosts].empty?
15
+ Stella.li plan.pretty
15
16
 
16
17
  client = Stella::Client.new opts[:hosts].first
17
18
  client.add_observer(self)
18
19
  client.enable_benchmark_mode if opts[:benchmark]
19
20
 
21
+ Stella.li $/, "Starting test...", $/
22
+ Drydock::Screen.flush
23
+ sleep 0.2
24
+
20
25
  plan.usecases.each_with_index do |uc,i|
21
26
  desc = (uc.desc || "Usecase ##{i+1}")
22
27
  Stella.li ' %-65s '.att(:reverse).bright % [desc]
@@ -24,6 +29,8 @@ module Stella::Engine
24
29
  end
25
30
 
26
31
  Drydock::Screen.flush
32
+
33
+ !plan.errors?
27
34
  end
28
35
 
29
36
  end
@@ -16,12 +16,21 @@ module Stella::Engine
16
16
  opts[:users] = 1000 if opts[:users] > 1000
17
17
 
18
18
  Stella.ld "OPTIONS: #{opts.inspect}"
19
- Stella.li2 "Hosts: " << opts[:hosts].join(', ')
19
+ Stella.li3 "Hosts: " << opts[:hosts].join(', ')
20
+ Stella.li2 plan.pretty
20
21
 
21
22
  packages = build_thread_package plan, opts
23
+ Stella.li $/, "Prepared #{packages.size} virtual users..."
22
24
  Drydock::Screen.flush
23
25
 
26
+
27
+ Stella.li $/, "Starting test...", $/
28
+ Drydock::Screen.flush
29
+ sleep 0.2
30
+
24
31
  Thread.ify packages, :threads => opts[:users] do |package|
32
+ # TEMPFIX. The fill in build_thread_package is creating nil elements
33
+ next if package.nil?
25
34
  (1..opts[:repetitions]).to_a.each do |rep|
26
35
  # We store client specific data in the usecase
27
36
  # so we clone it here so each thread is unique.
@@ -31,6 +40,8 @@ module Stella::Engine
31
40
  end
32
41
 
33
42
  Drydock::Screen.flush
43
+
44
+ !plan.errors?
34
45
  end
35
46
 
36
47
  protected
@@ -42,7 +53,7 @@ module Stella::Engine
42
53
  @index, @client, @usecase = i, c, u
43
54
  end
44
55
  end
45
-
56
+
46
57
  def build_thread_package(plan, opts)
47
58
  packages, pointer = Array.new(opts[:users]), 0
48
59
  plan.usecases.each_with_index do |usecase,i|
@@ -78,7 +89,7 @@ module Stella::Engine
78
89
 
79
90
  def update_receive_response(client_id, usecase, meth, uri, req, params, container)
80
91
  desc = "#{usecase.desc} > #{req.desc}"
81
- Stella.li ' Client%-3s %3d %-6s %-45s %s' % [client_id, container.status, req.http_method, desc, uri]
92
+ Stella.li2 ' Client%-3s %3d %-6s %-45s %s' % [client_id, container.status, req.http_method, desc, uri]
82
93
  end
83
94
 
84
95
  def update_execute_response_handler(client_id, req, container)
@@ -7,17 +7,28 @@ class Testplan
7
7
 
8
8
  class WackyRatio < Stella::Error
9
9
  end
10
-
11
10
 
12
11
  attr_accessor :usecases
13
12
  attr_accessor :base_path
14
13
  attr_accessor :desc
15
14
  attr_reader :stats
16
15
 
17
- def initialize
16
+ def initialize(uris=[], opts={})
18
17
  @desc, @usecases = "Stella's plan", []
19
18
  @testplan_current_ratio = 0
20
19
  @stats = Stella::Testplan::Stats.new
20
+
21
+ unless uris.empty?
22
+ usecase = Stella::Testplan::Usecase.new
23
+ usecase.ratio = 1.0
24
+ uris.each do |uri|
25
+ uri = URI.parse uri
26
+ uri.path = '/' if uri.path.empty?
27
+ req = usecase.add_request :get, uri.path
28
+ req.wait = opts[:delay] if opts[:delay]
29
+ end
30
+ self.add_usecase usecase
31
+ end
21
32
  end
22
33
 
23
34
  def self.load_file(path)
@@ -29,6 +40,12 @@ class Testplan
29
40
  plan
30
41
  end
31
42
 
43
+ # Were there any errors in any of the usecases?
44
+ def errors?
45
+ Stella.ld "TODO: tally use case errors"
46
+ false
47
+ end
48
+
32
49
  def check!
33
50
  # Adjust ratios if necessary
34
51
  needy = @usecases.select { |u| u.ratio == -1 }
@@ -74,7 +91,7 @@ class Testplan
74
91
  str << " %-50s ".att(:reverse) % [@desc]
75
92
  @usecases.each_with_index do |uc,i|
76
93
  description = uc.desc || "Usecase ##{i+1}"
77
- str << " %s (%s)".bright % [description, uc.ratio]
94
+ str << " %s (%s%%)".bright % [description, uc.ratio_pretty]
78
95
  requests = uc.requests.each do |r|
79
96
  str << " %-35s %s" % ["#{r.desc}:", r]
80
97
  if Stella.loglev > 2
@@ -3,14 +3,34 @@
3
3
  module Stella
4
4
  class Testplan
5
5
 
6
+ #
7
+ # Any valid Ruby syntax will do the trick:
8
+ #
9
+ # usecase(10, "Self-serve") {
10
+ # post("/listing/add", "Add a listing") {
11
+ # wait 1..4
12
+ # param :name => random(8)
13
+ # param :city => "Vancouver"
14
+ # response(302) {
15
+ # repeat 3
16
+ # }
17
+ # }
18
+ # }
19
+ #
6
20
  class Usecase
7
21
  include Gibbler::Complex
22
+
8
23
  attr_accessor :desc
9
- attr_accessor :requests
10
24
  attr_writer :ratio
25
+
26
+ attr_accessor :requests
11
27
  attr_accessor :resources
12
28
  attr_accessor :base_path
13
29
 
30
+ class UnknownResource < Stella::Error
31
+ def message; "UnknownResource: #{@obj}"; end
32
+ end
33
+
14
34
  def initialize(&blk)
15
35
  @requests, @resources = [], {}
16
36
  instance_eval &blk unless blk.nil?
@@ -32,6 +52,11 @@ class Testplan
32
52
  r
33
53
  end
34
54
 
55
+ def ratio_pretty
56
+ r = (@ratio || 0).to_f
57
+ r > 1.0 ? r.to_i : (r * 100).to_i
58
+ end
59
+
35
60
  # Reads the contents of the file <tt>path</tt> (the current working
36
61
  # directory is assumed to be the same directory containing the test plan).
37
62
  def file(path)
@@ -64,4 +89,4 @@ class Testplan
64
89
 
65
90
  end
66
91
  end
67
- end
92
+ end
@@ -6,7 +6,7 @@ module Stella
6
6
  MAJOR = 0.freeze
7
7
  MINOR = 7.freeze
8
8
  TINY = 0.freeze
9
- PATCH = '003'.freeze
9
+ PATCH = '004'.freeze
10
10
  end
11
11
  def self.to_s; [MAJOR, MINOR, TINY].join('.'); end
12
12
  def self.to_f; self.to_s.to_f; end
@@ -1,7 +1,7 @@
1
1
  @spec = Gem::Specification.new do |s|
2
2
  s.name = "stella"
3
3
  s.rubyforge_project = 'stella'
4
- s.version = "0.7.0.003"
4
+ s.version = "0.7.0.004"
5
5
  s.summary = "Stella: Your friend in performance testing."
6
6
  s.description = s.summary
7
7
  s.author = "Delano Mandelbaum"
@@ -29,8 +29,10 @@
29
29
  README.rdoc
30
30
  Rakefile
31
31
  bin/stella
32
- examples/basic/listing_ids.csv
33
32
  examples/basic/plan.rb
33
+ examples/basic/search_terms.csv
34
+ examples/example_webapp.rb
35
+ examples/exceptions/plan.rb
34
36
  lib/stella.rb
35
37
  lib/stella/cli.rb
36
38
  lib/stella/client.rb
@@ -56,7 +58,6 @@
56
58
  lib/stella/version.rb
57
59
  lib/threadify.rb
58
60
  stella.gemspec
59
- support/example_webapp.rb
60
61
  support/useragents.txt
61
62
  )
62
63
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solutious-stella
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0.003
4
+ version: 0.7.0.004
5
5
  platform: ruby
6
6
  authors:
7
7
  - Delano Mandelbaum
@@ -78,8 +78,10 @@ files:
78
78
  - README.rdoc
79
79
  - Rakefile
80
80
  - bin/stella
81
- - examples/basic/listing_ids.csv
82
81
  - examples/basic/plan.rb
82
+ - examples/basic/search_terms.csv
83
+ - examples/example_webapp.rb
84
+ - examples/exceptions/plan.rb
83
85
  - lib/stella.rb
84
86
  - lib/stella/cli.rb
85
87
  - lib/stella/client.rb
@@ -105,7 +107,6 @@ files:
105
107
  - lib/stella/version.rb
106
108
  - lib/threadify.rb
107
109
  - stella.gemspec
108
- - support/example_webapp.rb
109
110
  - support/useragents.txt
110
111
  has_rdoc: true
111
112
  homepage: http://solutious.com/projects/stella/
@@ -1,7 +0,0 @@
1
- 1000
2
- 1001
3
- 1002
4
- 1003
5
- 1004
6
- 1005
7
- 1006