stormforge-ruby 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.
- checksums.yaml +7 -0
 - data/.gitignore +5 -0
 - data/.rspec +2 -0
 - data/.travis.yml +17 -0
 - data/Gemfile +4 -0
 - data/Guardfile +6 -0
 - data/LICENSE.txt +22 -0
 - data/README.md +72 -0
 - data/Rakefile +6 -0
 - data/bin/stormforge +18 -0
 - data/examples/README.md +27 -0
 - data/examples/Stormfile +1 -0
 - data/examples/demo_case.rb +12 -0
 - data/examples/file_upload.rb +28 -0
 - data/examples/fixtures/requests.csv +2 -0
 - data/examples/fixtures/users.csv +2 -0
 - data/examples/simple_and_long.rb +12 -0
 - data/examples/simple_and_short.rb +12 -0
 - data/examples/test_case_definition_v1.rb +206 -0
 - data/lib/core_ext/fixnum.rb +20 -0
 - data/lib/stormforge.rb +39 -0
 - data/lib/stormforge/client.rb +227 -0
 - data/lib/stormforge/dsl.rb +4 -0
 - data/lib/stormforge/dsl/test_case.rb +9 -0
 - data/lib/stormforge/dsl/test_case/attribute_access.rb +19 -0
 - data/lib/stormforge/dsl/test_case/cloud.rb +33 -0
 - data/lib/stormforge/dsl/test_case/data_source.rb +49 -0
 - data/lib/stormforge/dsl/test_case/data_source/file_fixture.rb +104 -0
 - data/lib/stormforge/dsl/test_case/data_source/random_number.rb +64 -0
 - data/lib/stormforge/dsl/test_case/data_source/random_string.rb +52 -0
 - data/lib/stormforge/dsl/test_case/definition.rb +128 -0
 - data/lib/stormforge/dsl/test_case/session.rb +77 -0
 - data/lib/stormforge/registry.rb +23 -0
 - data/lib/stormforge/version.rb +3 -0
 - data/lib/thor/generators/init.rb +14 -0
 - data/lib/thor/generators/templates/Stormfile +15 -0
 - data/lib/thor/main.rb +79 -0
 - data/lib/thor/storm_forge_base.rb +46 -0
 - data/lib/thor/testcase.rb +23 -0
 - data/lib/thor/testrun.rb +75 -0
 - data/spec/client_spec.rb +4 -0
 - data/spec/dsl/test_case/attribute_access_spec.rb +46 -0
 - data/spec/dsl/test_case/cloud_spec.rb +15 -0
 - data/spec/dsl/test_case/data_source/file_fixture_spec.rb +101 -0
 - data/spec/dsl/test_case/data_source/random_number_spec.rb +51 -0
 - data/spec/dsl/test_case/data_source/random_string_spec.rb +33 -0
 - data/spec/dsl/test_case/data_source_spec.rb +12 -0
 - data/spec/dsl/test_case/definition_spec.rb +107 -0
 - data/spec/dsl/test_case/session_spec.rb +102 -0
 - data/spec/dsl/test_case_integration_spec.rb +148 -0
 - data/spec/dsl/test_case_spec.rb +8 -0
 - data/spec/fixtures/ip_addresses.csv +1 -0
 - data/spec/fixtures/slug.csv +1 -0
 - data/spec/fixtures/users.csv +2 -0
 - data/spec/registry_spec.rb +34 -0
 - data/spec/spec_helper.rb +33 -0
 - data/spec/stormforger_spec.rb +13 -0
 - data/stormforge_ruby.gemspec +38 -0
 - metadata +344 -0
 
| 
         @@ -0,0 +1,64 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
              class StormForge::Dsl::TestCase::DataSource::RandomNumber
         
     | 
| 
      
 2 
     | 
    
         
            +
                class InvalidRange < Exception; end;
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
                include ActiveModel::Serializers::JSON
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
                self.include_root_in_json = false
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
                attr_reader :name, :range
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                def initialize(name, options)
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @name = name
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @sequence = 0
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                  @range = ensure_range(options.fetch(:range, nil))
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                def next
         
     | 
| 
      
 18 
     | 
    
         
            +
                  args = {
         
     | 
| 
      
 19 
     | 
    
         
            +
                    t: "random_number",
         
     | 
| 
      
 20 
     | 
    
         
            +
                    n: name,
         
     | 
| 
      
 21 
     | 
    
         
            +
                    seq: (@sequence += 1)
         
     | 
| 
      
 22 
     | 
    
         
            +
                  }
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                  StormForge::Dsl::TestCase::DataSource.build_marker("GENERATOR", args)
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
                def count
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @sequence
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                def serializable_hash(*args)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  {
         
     | 
| 
      
 33 
     | 
    
         
            +
                    name: name,
         
     | 
| 
      
 34 
     | 
    
         
            +
                    type: "random_number",
         
     | 
| 
      
 35 
     | 
    
         
            +
                    range: [range.begin, range.end]
         
     | 
| 
      
 36 
     | 
    
         
            +
                  }
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                private
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                # TODO this is not so nice, to raise when called from #initialize
         
     | 
| 
      
 42 
     | 
    
         
            +
                # This should be changed to validation errors.
         
     | 
| 
      
 43 
     | 
    
         
            +
                def ensure_range(range)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  unless range
         
     | 
| 
      
 45 
     | 
    
         
            +
                    raise InvalidRange.new("Range of RandomNumber cannot be nil!")
         
     | 
| 
      
 46 
     | 
    
         
            +
                  end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                  if range.instance_of?(Array) && range.size == 2
         
     | 
| 
      
 49 
     | 
    
         
            +
                    range = range[0]..range[1]
         
     | 
| 
      
 50 
     | 
    
         
            +
                  end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  if range.instance_of?(Range)
         
     | 
| 
      
 53 
     | 
    
         
            +
                    unless range.begin.instance_of?(Fixnum) && range.end.instance_of?(Fixnum)
         
     | 
| 
      
 54 
     | 
    
         
            +
                      raise InvalidRange.new("Range of RandomNumber needs to be range of Fixnums, between 0 and 100000000")
         
     | 
| 
      
 55 
     | 
    
         
            +
                    end
         
     | 
| 
      
 56 
     | 
    
         
            +
                    unless range.begin > 0 && range.end <= 100_000_000
         
     | 
| 
      
 57 
     | 
    
         
            +
                      raise InvalidRange.new("Range of RandomNumber needs to be between 0 and 100000000")
         
     | 
| 
      
 58 
     | 
    
         
            +
                    end
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
                  range
         
     | 
| 
      
 62 
     | 
    
         
            +
                end
         
     | 
| 
      
 63 
     | 
    
         
            +
              end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,52 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class StormForge::Dsl::TestCase::DataSource::RandomString
         
     | 
| 
      
 2 
     | 
    
         
            +
              include ActiveModel::Serializers::JSON
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              self.include_root_in_json = false
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              attr_reader :name, :length
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              def initialize(name, options)
         
     | 
| 
      
 9 
     | 
    
         
            +
                @name = name
         
     | 
| 
      
 10 
     | 
    
         
            +
                @sequence = 0
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                @length = ensure_length(options.fetch(:length, 1))
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              def next
         
     | 
| 
      
 16 
     | 
    
         
            +
                args = {
         
     | 
| 
      
 17 
     | 
    
         
            +
                  t: "random_string",
         
     | 
| 
      
 18 
     | 
    
         
            +
                  n: name,
         
     | 
| 
      
 19 
     | 
    
         
            +
                  seq: (@sequence += 1)
         
     | 
| 
      
 20 
     | 
    
         
            +
                }
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                StormForge::Dsl::TestCase::DataSource.build_marker("GENERATOR", args)
         
     | 
| 
      
 23 
     | 
    
         
            +
              end
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
              def count
         
     | 
| 
      
 26 
     | 
    
         
            +
                @sequence
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
              def serializable_hash(*args)
         
     | 
| 
      
 30 
     | 
    
         
            +
                {
         
     | 
| 
      
 31 
     | 
    
         
            +
                  name: name,
         
     | 
| 
      
 32 
     | 
    
         
            +
                  type: "random_string",
         
     | 
| 
      
 33 
     | 
    
         
            +
                  length: @length
         
     | 
| 
      
 34 
     | 
    
         
            +
                }
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
              private
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
              # TODO casting the length to an integer is not ideal.
         
     | 
| 
      
 41 
     | 
    
         
            +
              # We should have validations here too, like for other
         
     | 
| 
      
 42 
     | 
    
         
            +
              # DSL related objects.
         
     | 
| 
      
 43 
     | 
    
         
            +
              def ensure_length(length)
         
     | 
| 
      
 44 
     | 
    
         
            +
                length = length.to_i
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
                return   1 if length < 1
         
     | 
| 
      
 47 
     | 
    
         
            +
                return 100 if length > 100
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                length
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
            end
         
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,128 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class StormForge::Dsl::TestCase::Definition
         
     | 
| 
      
 2 
     | 
    
         
            +
              include ActiveModel::Validations
         
     | 
| 
      
 3 
     | 
    
         
            +
              include ActiveModel::Serializers::JSON
         
     | 
| 
      
 4 
     | 
    
         
            +
              include ::StormForge::Dsl::TestCase::AttributeAccess
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              attr_reader :attributes
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              self.include_root_in_json = true
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              validates_presence_of :title
         
     | 
| 
      
 11 
     | 
    
         
            +
              validates_presence_of :version
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              validates :targets, :length => {
         
     | 
| 
      
 14 
     | 
    
         
            +
                minimum: 1,
         
     | 
| 
      
 15 
     | 
    
         
            +
                too_short: "at least one target is required"
         
     | 
| 
      
 16 
     | 
    
         
            +
              }
         
     | 
| 
      
 17 
     | 
    
         
            +
              validates :arrival_phases, :length => {
         
     | 
| 
      
 18 
     | 
    
         
            +
                minimum: 1,
         
     | 
| 
      
 19 
     | 
    
         
            +
                too_short: "at least one arrival phase is required"
         
     | 
| 
      
 20 
     | 
    
         
            +
              }
         
     | 
| 
      
 21 
     | 
    
         
            +
              validates :sessions, :length => {
         
     | 
| 
      
 22 
     | 
    
         
            +
                minimum: 1,
         
     | 
| 
      
 23 
     | 
    
         
            +
                too_short: "at least one session is required"
         
     | 
| 
      
 24 
     | 
    
         
            +
              }
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
              class SessionProbabilityValidator < ActiveModel::EachValidator
         
     | 
| 
      
 27 
     | 
    
         
            +
                def validate_each(record, attr, value)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  total_probability = value.values.map(&:probability).sum
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  record.errors.add attr, "the total probability of all sessions need to be 100%" unless total_probability == 100
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
              validates :sessions, :session_probability => true
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
              def initialize(slug, callable=nil, &block)
         
     | 
| 
      
 38 
     | 
    
         
            +
                @slug = slug
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                @attributes = {
         
     | 
| 
      
 41 
     | 
    
         
            +
                  arrival_phases: [],
         
     | 
| 
      
 42 
     | 
    
         
            +
                  sessions: {},
         
     | 
| 
      
 43 
     | 
    
         
            +
                  data_sources: nil
         
     | 
| 
      
 44 
     | 
    
         
            +
                }
         
     | 
| 
      
 45 
     | 
    
         
            +
                instance_eval(&(callable || block))
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
              # TODO this feels messy. is this really the only way to serialize complex attributes?
         
     | 
| 
      
 49 
     | 
    
         
            +
              def read_attribute_for_serialization(key)
         
     | 
| 
      
 50 
     | 
    
         
            +
                case key
         
     | 
| 
      
 51 
     | 
    
         
            +
                when :sessions
         
     | 
| 
      
 52 
     | 
    
         
            +
                  @attributes[:sessions].each_with_object({}) do |(session_name, session), hash|
         
     | 
| 
      
 53 
     | 
    
         
            +
                    hash[session_name] = session.serializable_hash
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
                when :cloud
         
     | 
| 
      
 56 
     | 
    
         
            +
                  @attributes[:cloud].serializable_hash
         
     | 
| 
      
 57 
     | 
    
         
            +
                when :data_sources
         
     | 
| 
      
 58 
     | 
    
         
            +
                  @attributes[:data_sources].serializable_hash if @attributes[:data_sources]
         
     | 
| 
      
 59 
     | 
    
         
            +
                else
         
     | 
| 
      
 60 
     | 
    
         
            +
                  super
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
      
 62 
     | 
    
         
            +
              end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
              def as_json(*args)
         
     | 
| 
      
 65 
     | 
    
         
            +
                hash = super
         
     | 
| 
      
 66 
     | 
    
         
            +
                hash["dsl_version"] = self.dsl_version.to_s
         
     | 
| 
      
 67 
     | 
    
         
            +
                hash["slug"] = slug
         
     | 
| 
      
 68 
     | 
    
         
            +
                hash
         
     | 
| 
      
 69 
     | 
    
         
            +
              end
         
     | 
| 
      
 70 
     | 
    
         
            +
             
     | 
| 
      
 71 
     | 
    
         
            +
              def targets(*targets)
         
     | 
| 
      
 72 
     | 
    
         
            +
                return @attributes[:targets] if targets.length == 0
         
     | 
| 
      
 73 
     | 
    
         
            +
                @attributes[:targets] = targets.flatten
         
     | 
| 
      
 74 
     | 
    
         
            +
              end
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
              ARRIVAL_PHASE_OPTIONS = {
         
     | 
| 
      
 77 
     | 
    
         
            +
                warmup:        false,
         
     | 
| 
      
 78 
     | 
    
         
            +
             
     | 
| 
      
 79 
     | 
    
         
            +
                duration:      nil,
         
     | 
| 
      
 80 
     | 
    
         
            +
                duration_unit: :second,
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
                rate:          nil,
         
     | 
| 
      
 83 
     | 
    
         
            +
                rate_unit:     :second
         
     | 
| 
      
 84 
     | 
    
         
            +
              }.freeze
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
              def arrival_phase(options)
         
     | 
| 
      
 87 
     | 
    
         
            +
                @attributes[:arrival_phases] << ARRIVAL_PHASE_OPTIONS.each_with_object({}) do |(option, default), phase_options|
         
     | 
| 
      
 88 
     | 
    
         
            +
                  phase_options[option] = options.fetch(option, default)
         
     | 
| 
      
 89 
     | 
    
         
            +
                end
         
     | 
| 
      
 90 
     | 
    
         
            +
              end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
              def session(name, probability, callable=nil, &block)
         
     | 
| 
      
 93 
     | 
    
         
            +
                @attributes[:sessions][name] = StormForge::Dsl::TestCase::Session.new(name, probability, @attributes[:data_sources], &(callable || block))
         
     | 
| 
      
 94 
     | 
    
         
            +
              end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
              def available_attributes
         
     | 
| 
      
 97 
     | 
    
         
            +
                [
         
     | 
| 
      
 98 
     | 
    
         
            +
                  :title,
         
     | 
| 
      
 99 
     | 
    
         
            +
                  :description,
         
     | 
| 
      
 100 
     | 
    
         
            +
                  :version,
         
     | 
| 
      
 101 
     | 
    
         
            +
                  :targets,
         
     | 
| 
      
 102 
     | 
    
         
            +
                  :arrival_phases,
         
     | 
| 
      
 103 
     | 
    
         
            +
                  :sessions,
         
     | 
| 
      
 104 
     | 
    
         
            +
                  :cloud,
         
     | 
| 
      
 105 
     | 
    
         
            +
                  :data_sources
         
     | 
| 
      
 106 
     | 
    
         
            +
                ]
         
     | 
| 
      
 107 
     | 
    
         
            +
              end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
              def cloud(provider, callable=nil, &block)
         
     | 
| 
      
 110 
     | 
    
         
            +
                @attributes[:cloud] = StormForge::Dsl::TestCase::Cloud.new(provider, &(callable || block))
         
     | 
| 
      
 111 
     | 
    
         
            +
              end
         
     | 
| 
      
 112 
     | 
    
         
            +
             
     | 
| 
      
 113 
     | 
    
         
            +
              def data_sources(callable=nil, &block)
         
     | 
| 
      
 114 
     | 
    
         
            +
                if (callable || block)
         
     | 
| 
      
 115 
     | 
    
         
            +
                  @attributes[:data_sources] = StormForge::Dsl::TestCase::DataSource.new(&(callable || block))
         
     | 
| 
      
 116 
     | 
    
         
            +
                else
         
     | 
| 
      
 117 
     | 
    
         
            +
                  @attributes[:data_sources]
         
     | 
| 
      
 118 
     | 
    
         
            +
                end
         
     | 
| 
      
 119 
     | 
    
         
            +
              end
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
      
 121 
     | 
    
         
            +
              def slug
         
     | 
| 
      
 122 
     | 
    
         
            +
                @slug.to_s.downcase.gsub(/[^a-z0-9\-_]/, "_").gsub(/_+/, "_")
         
     | 
| 
      
 123 
     | 
    
         
            +
              end
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
              def dsl_version
         
     | 
| 
      
 126 
     | 
    
         
            +
                :v1
         
     | 
| 
      
 127 
     | 
    
         
            +
              end
         
     | 
| 
      
 128 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,77 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class StormForge::Dsl::TestCase::Session
         
     | 
| 
      
 2 
     | 
    
         
            +
              include ActiveModel::Validations
         
     | 
| 
      
 3 
     | 
    
         
            +
              include ActiveModel::Serializers::JSON
         
     | 
| 
      
 4 
     | 
    
         
            +
              include ::StormForge::Dsl::TestCase::AttributeAccess
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              self.include_root_in_json = false
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              attr_reader :attributes, :data_sources
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              validates :name, presence: true
         
     | 
| 
      
 11 
     | 
    
         
            +
              validates :probability, :inclusion => 1..100, :numericality => true
         
     | 
| 
      
 12 
     | 
    
         
            +
              validates :steps, :length => {
         
     | 
| 
      
 13 
     | 
    
         
            +
                minimum: 1,
         
     | 
| 
      
 14 
     | 
    
         
            +
                too_short: "at least one step is required"
         
     | 
| 
      
 15 
     | 
    
         
            +
              }
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
              def initialize(name, probability, data_sources, callable=nil, &block)
         
     | 
| 
      
 18 
     | 
    
         
            +
                @attributes = {
         
     | 
| 
      
 19 
     | 
    
         
            +
                  name: name,
         
     | 
| 
      
 20 
     | 
    
         
            +
                  probability: probability,
         
     | 
| 
      
 21 
     | 
    
         
            +
                  steps: []
         
     | 
| 
      
 22 
     | 
    
         
            +
                }
         
     | 
| 
      
 23 
     | 
    
         
            +
                @data_sources = data_sources
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                instance_eval(&(callable || block))
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              def available_attributes
         
     | 
| 
      
 29 
     | 
    
         
            +
                [
         
     | 
| 
      
 30 
     | 
    
         
            +
                  :name,
         
     | 
| 
      
 31 
     | 
    
         
            +
                  :probability,
         
     | 
| 
      
 32 
     | 
    
         
            +
                  :description,
         
     | 
| 
      
 33 
     | 
    
         
            +
                  :steps
         
     | 
| 
      
 34 
     | 
    
         
            +
                ]
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
              [:head, :get, :post, :delete, :put, :options, :patch].each do |request_method|
         
     | 
| 
      
 38 
     | 
    
         
            +
                define_method(request_method) do |path, options={}|
         
     | 
| 
      
 39 
     | 
    
         
            +
                  request(request_method, path, options)
         
     | 
| 
      
 40 
     | 
    
         
            +
                end
         
     | 
| 
      
 41 
     | 
    
         
            +
              end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
              def wait(seconds)
         
     | 
| 
      
 44 
     | 
    
         
            +
                @attributes[:steps] << {
         
     | 
| 
      
 45 
     | 
    
         
            +
                  action: :wait,
         
     | 
| 
      
 46 
     | 
    
         
            +
                  duration: seconds
         
     | 
| 
      
 47 
     | 
    
         
            +
                }
         
     | 
| 
      
 48 
     | 
    
         
            +
              end
         
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
              def pick(what)
         
     | 
| 
      
 51 
     | 
    
         
            +
                raise "DataSource #{what} not defined!" unless @data_sources.sources[what]
         
     | 
| 
      
 52 
     | 
    
         
            +
                @data_sources.sources[what].next
         
     | 
| 
      
 53 
     | 
    
         
            +
              end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
              def serializable_hash(*args)
         
     | 
| 
      
 56 
     | 
    
         
            +
                hash = super
         
     | 
| 
      
 57 
     | 
    
         
            +
                hash[:data_sources_usage] = if data_sources.present?
         
     | 
| 
      
 58 
     | 
    
         
            +
                                              data_sources.sources.each_with_object({}) do |(name, source), memo|
         
     | 
| 
      
 59 
     | 
    
         
            +
                                                memo[name] = source.count
         
     | 
| 
      
 60 
     | 
    
         
            +
                                              end
         
     | 
| 
      
 61 
     | 
    
         
            +
                                            else
         
     | 
| 
      
 62 
     | 
    
         
            +
                                              {}
         
     | 
| 
      
 63 
     | 
    
         
            +
                                            end
         
     | 
| 
      
 64 
     | 
    
         
            +
                hash
         
     | 
| 
      
 65 
     | 
    
         
            +
              end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
              private
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
              def request(method, path, options={})
         
     | 
| 
      
 70 
     | 
    
         
            +
                @attributes[:steps] << {
         
     | 
| 
      
 71 
     | 
    
         
            +
                  action: :request,
         
     | 
| 
      
 72 
     | 
    
         
            +
                  method: method.to_sym,
         
     | 
| 
      
 73 
     | 
    
         
            +
                  path: path,
         
     | 
| 
      
 74 
     | 
    
         
            +
                  options: options
         
     | 
| 
      
 75 
     | 
    
         
            +
                }
         
     | 
| 
      
 76 
     | 
    
         
            +
              end
         
     | 
| 
      
 77 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,23 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class StormForge::Registry
         
     | 
| 
      
 2 
     | 
    
         
            +
              include Enumerable
         
     | 
| 
      
 3 
     | 
    
         
            +
              extend Forwardable
         
     | 
| 
      
 4 
     | 
    
         
            +
              def_delegator :test_cases, :each
         
     | 
| 
      
 5 
     | 
    
         
            +
              def_delegator :test_cases, :each_pair
         
     | 
| 
      
 6 
     | 
    
         
            +
              def_delegator :test_cases, :[]
         
     | 
| 
      
 7 
     | 
    
         
            +
              def_delegator :test_cases, :[], :get
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              def define(slug, callable=nil, &block)
         
     | 
| 
      
 10 
     | 
    
         
            +
                test_cases[slug] = StormForge::Dsl::TestCase.define(slug, callable || block)
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
              def reset!
         
     | 
| 
      
 14 
     | 
    
         
            +
                @test_cases = {}
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
              private
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
              def test_cases
         
     | 
| 
      
 21 
     | 
    
         
            +
                @test_cases ||= {}
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,14 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class StormForge::Thor::Init < Thor::Group
         
     | 
| 
      
 2 
     | 
    
         
            +
              include Thor::Actions
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              argument :path, :default => "."
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              def self.source_root
         
     | 
| 
      
 7 
     | 
    
         
            +
                File.join(File.dirname(__FILE__), "templates")
         
     | 
| 
      
 8 
     | 
    
         
            +
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              def stormfile
         
     | 
| 
      
 11 
     | 
    
         
            +
                copy_file "Stormfile", File.join(path, "Stormfile")
         
     | 
| 
      
 12 
     | 
    
         
            +
              end
         
     | 
| 
      
 13 
     | 
    
         
            +
            end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,15 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # uncomment this line, to load all test cases in 'test_cases/*'
         
     | 
| 
      
 2 
     | 
    
         
            +
            # Dir[File.join(File.dirname(__FILE__), "test_cases/*.rb")].each { |test_case| load test_case }
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            StormForge.define_case :getting_started do
         
     | 
| 
      
 5 
     | 
    
         
            +
              title "My first test case"
         
     | 
| 
      
 6 
     | 
    
         
            +
              version "0.1"
         
     | 
| 
      
 7 
     | 
    
         
            +
              description "Simple and short test to examine test run process."
         
     | 
| 
      
 8 
     | 
    
         
            +
              targets "stage.example.com"
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              arrival_phase duration: 5.minutes, rate: 1.per_second
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              session "hit start page", 100.percent do
         
     | 
| 
      
 13 
     | 
    
         
            +
                get "/"
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/thor/main.rb
    ADDED
    
    | 
         @@ -0,0 +1,79 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require "io/console"
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            class StormForge::Thor::Main < StormForge::Thor::Base
         
     | 
| 
      
 4 
     | 
    
         
            +
              class_option :authfile, type: :string, desc: "StormForger authentication file", default: "~/.stormforger"
         
     | 
| 
      
 5 
     | 
    
         
            +
              class_option :stormfile, type: :string, desc: "Stormfile location", default: "./Stormfile"
         
     | 
| 
      
 6 
     | 
    
         
            +
              class_option :development, type: :boolean, desc: "run in development mode"
         
     | 
| 
      
 7 
     | 
    
         
            +
              class_option :skip_ssl_verify, type: :boolean, desc: "skip SSL verification", default: false
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              register StormForge::Thor::Init, "init", "init [PATH]", "Initialize new Stormfile"
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              desc "version", "Display version information"
         
     | 
| 
      
 12 
     | 
    
         
            +
              def version
         
     | 
| 
      
 13 
     | 
    
         
            +
                say "stormforge-ruby version #{StormForge::VERSION}"
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              desc "auth", "Aquire an API token"
         
     | 
| 
      
 17 
     | 
    
         
            +
              method_option :email, type: :string, desc: 'Your email'
         
     | 
| 
      
 18 
     | 
    
         
            +
              method_option :password, type: :string, desc: 'Your password'
         
     | 
| 
      
 19 
     | 
    
         
            +
              def auth
         
     | 
| 
      
 20 
     | 
    
         
            +
                error "WARNING! Disabling SSL certificate verification is not recommended. Proceed with caution!" if options[:skip_ssl_verify]
         
     | 
| 
      
 21 
     | 
    
         
            +
                email, password = options[:email], options[:password]
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                email = ask("Please enter your email:") unless email
         
     | 
| 
      
 24 
     | 
    
         
            +
                raise Thor::Error, "You must enter a value for that field." if email.empty?
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                # TODO: replace with ask("...", :echo => false) if thor gets a new release
         
     | 
| 
      
 27 
     | 
    
         
            +
                print "Please enter your password: "
         
     | 
| 
      
 28 
     | 
    
         
            +
                password = STDIN.noecho(&:gets).chomp unless password
         
     | 
| 
      
 29 
     | 
    
         
            +
                print "\n"
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                raise Thor::Error, "You must enter a value for that field." if password.empty?
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                say "Attempting to aquire API token..."
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                token = StormForge::Client.new(dev_mode: options[:development], skip_ssl_verify: options[:skip_ssl_verify]).aquire_api_token(email, password)
         
     | 
| 
      
 36 
     | 
    
         
            +
                raise Thor::Error, "Authentication error!" unless token
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
                say "Your API authentication is: #{token}", :yellow
         
     | 
| 
      
 39 
     | 
    
         
            +
                unless options[:authfile] == "-"
         
     | 
| 
      
 40 
     | 
    
         
            +
                  say "Writing/Updating credentials to #{authfile}"
         
     | 
| 
      
 41 
     | 
    
         
            +
                  File.open(authfile, "w+") do |file|
         
     | 
| 
      
 42 
     | 
    
         
            +
                    file.puts({ email: email, authentication_token: token, created_at: Time.now }.to_json)
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
                  File.chmod(0600, authfile)
         
     | 
| 
      
 45 
     | 
    
         
            +
                end
         
     | 
| 
      
 46 
     | 
    
         
            +
              rescue Faraday::Error::ConnectionFailed => e
         
     | 
| 
      
 47 
     | 
    
         
            +
                p e.message
         
     | 
| 
      
 48 
     | 
    
         
            +
                raise Thor::Error, "StormForger API not available :-/ (connection failed)"
         
     | 
| 
      
 49 
     | 
    
         
            +
              rescue Faraday::Error::ClientError => e
         
     | 
| 
      
 50 
     | 
    
         
            +
                if e.response[:status] == 401
         
     | 
| 
      
 51 
     | 
    
         
            +
                  handle_authentication_error(e)
         
     | 
| 
      
 52 
     | 
    
         
            +
                else
         
     | 
| 
      
 53 
     | 
    
         
            +
                  raise e
         
     | 
| 
      
 54 
     | 
    
         
            +
                end
         
     | 
| 
      
 55 
     | 
    
         
            +
              end
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
              desc "ping", "Ping StormForger API"
         
     | 
| 
      
 58 
     | 
    
         
            +
              def ping
         
     | 
| 
      
 59 
     | 
    
         
            +
                result = client.ping
         
     | 
| 
      
 60 
     | 
    
         
            +
                say "pong" if result.status < 399
         
     | 
| 
      
 61 
     | 
    
         
            +
              end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
              desc "curl", "print curl command template with authentication"
         
     | 
| 
      
 64 
     | 
    
         
            +
              def curl
         
     | 
| 
      
 65 
     | 
    
         
            +
                say "curl --user '#{authentication["email"]}:#{authentication["authentication_token"]}' -X GET #{client.url_base}"
         
     | 
| 
      
 66 
     | 
    
         
            +
              end
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
              desc "httpie", "print HTTPie command template with authentication"
         
     | 
| 
      
 69 
     | 
    
         
            +
              def httpie
         
     | 
| 
      
 70 
     | 
    
         
            +
                say "http --auth '#{authentication["email"]}:#{authentication["authentication_token"]}' GET #{client.url_base}"
         
     | 
| 
      
 71 
     | 
    
         
            +
              end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
              desc "testrun SUBCOMMAND ...ARGS", "Manage test runs"
         
     | 
| 
      
 74 
     | 
    
         
            +
              subcommand "testrun", StormForge::Thor::Testrun
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
              desc "testcase SUBCOMMAND ...ARGS", "Manage test cases"
         
     | 
| 
      
 77 
     | 
    
         
            +
              subcommand "testcase", StormForge::Thor::Testcase
         
     | 
| 
      
 78 
     | 
    
         
            +
            end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     |