Skip to content
This repository has been archived by the owner on Dec 4, 2020. It is now read-only.

Basic tests and parsing #1

Merged
merged 15 commits into from
Jun 4, 2018
Merged

Basic tests and parsing #1

merged 15 commits into from
Jun 4, 2018

Conversation

YashoSharma
Copy link
Contributor

@YashoSharma YashoSharma commented May 29, 2018

Description of changes:
Adds basic testing functionality and parsing of user inputs.

  • Adds test helper to assist in running integration tests
  • Adds basic integration test to ensure that generator functions and is compatible with aws-record
  • Adds unit tests for secondary index parsing
  • Adds secondary index parser
  • Adds unit tests for attribute parsing
  • Adds attribute parser

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@YashoSharma YashoSharma requested a review from awood45 May 29, 2018 23:08
Copy link
Contributor

@awood45 awood45 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Biggest question is probably on the SecondaryIndex class - could we start with generating models generally, then add secondary index support as an iteration?

COPYING Outdated
@@ -0,0 +1,202 @@

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a verbatim copy of the LICENSE file included in the project template. For those files, let's use what Henri generated.

Copy link
Contributor Author

@YashoSharma YashoSharma May 30, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The guidelines for applying the Apache 2.0 License said "It is also recommended that you include a file called COPYING with the same content" as the License. Is it necessary then to have this file?


spec.summary = "Rails generators for aws-record"
spec.description = "Provides generators that integrate aws-record models with Rails scaffolding"
spec.homepage = "https://github.com/awslabs"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/awslabs/aws-record-generator rather than just https://github.com/awslabs

spec.require_paths = ["lib"]
spec.files = Dir['lib/**/*.rb']

spec.add_dependency('aws-record', '~> 1.0')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's depend on aws-record ~> 2

We want to be using the latest major version, and since any changes within that version are backwards compatible, we do not want to also lock on the minor version as this statement does.

spec.files = Dir['lib/**/*.rb']

spec.add_dependency('aws-record', '~> 1.0')
spec.add_dependency('rails', '~> 5.2.0')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is far too narrow of a Rails dependency. Instead, we should understand the minimum version of Rails we need present to support generation (and indeed, possibly depend on a generator gem directly), and do a minimum dependency off that. See what we did in the aws-sdk-rails gemspec as an example.

@@ -0,0 +1,38 @@
# Copyright 2018-2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: It isn't 2019 yet, 2018-2018 is fine until 2019 comes.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment applies to all other files like this.

"""
id:hkey count:int:rkey
"""
When we run the ModelGenerator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this test imply we ran the ModelGenerator with zero options, and that the result is the table as specified?

We should circle back on this, I think what we would want to do for this test is probably this:

  When we run the ModelGenerator with the following options:
    """
    id:hkey count:int:rkey read_capacity:5 write_capacity:2
    """
  Then a "model" should be generated with contents:
    """
    string_attr :id, hash_key: true
    integer_attr :count, range_key: true
    """
  And a "table_config" should be generated with contents:
    """
    t.read_capacity_units 5
    t.write_capacity_units 2
    """

...or similar. As written, the 5 and 2 look like magic numbers, and we should require those numbers be specified in the generator.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll work on updating the integration tests to address the comments, but did we want the number of r/w units to default to some arbitrary number if the user doesn't provide them, or should those parameters always be explicitly defined?

t.write_capacity_units 2
"""

When we migrate the TableConfig
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should potentially make this a bit higher level. For example, what we are doing is running rails command line commands and expecting them to do certain things, this is true for the generator above. Perhaps they both actually look like this:

When we run the rails command line with the following command string:
  """
  table_config:migrate
  """

The above could then also be:

When we run the rails command line with the following command string:
  """
  generate aws_record:model id:hkey count:int:rkey read_capacity:5 write_capacity:2
  """

Essentially, it's just running Rails command line tools like a user would (substituting the commands in our design doc as appropriate, these are loose illustrations).

require 'generators/aws_record/model/model_generator'

Before do
@gen_helper = AwsRecord::GeneratorTestHelper.new(AwsRecord::ModelGenerator, File.expand_path("tmp"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if this is the appropriate scope for Cucumber tests from some of the other feedback. Is it possible, I'd wonder, to integration test actual rails command line commands? This is more relevant to the migration test (because as written it's redundant with aws-record integration tests), but something to look in to.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed by 0989628

require 'spec_helper'

module AwsRecord
describe SecondaryIndex do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel like I'm missing something. Are we writing the Secondary Index generator before the generation of the standard hash and range key? It looks like we meant this to be something else.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just wrote the simpler of the two parsers that the design document needed to orient myself with the new testing frameworks. I planned to write these two parsing modules before the core generator, but the SecondaryIndex will be integrated into the main generator at a later iteration.

@gen_helper.run_in_test_app cmd
end

Then("a {string} should be generated with contents:") do |generated_type, body|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion, but not a requirement: This is going to be a very fragile test. For example, whitespace changes will break it. There are three ways to deal with this:

  1. Don't deal with it. This does work, so I'm okay with moving on with this for now.
  2. Make this a file matching test. You'd create a fixtures folder with files which you could verify match the generated content. Something like Then("a {string} should be generated matching {string}") do |gtype, matching_filepath|
  3. Write focused tests to do pattern matches on subsections of the file, where it would contain some set of lines, but we don't care about the rest.

Despite it not solving the fragility problem, I'd actually lean towards solution 2.

end
end

it 'properly handles using options in combination with one another' do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way we wrote this test raises an interesting question, in that we should validate that we accept the combinations provided.

  • It is NOT valid for an attribute to be both a hash key and a range key.
  • Keys cannot be any type, there are a subset of acceptable types.

We should actually delete this test, and replace it with three tests:

  1. A set of cherry-picked, valid combinations of options.
  2. An attribute being both a hash key and a range key, which should raise an exception.
  3. An attribute being a hash key and also a map_attr type, which is invalid and should raise an exception (we can chat about how to write this logic going forward).

We should then ensure that the previous test's implied behavior that a hash key can be a string doesn't break.

type, opts = "string", type if OPTS.any? { |opt| type.include? opt }

opts = opts.split(',') if opts
type, opts = *parse_type_and_options(type, opts)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think after this, right before initialization, is a good time for a validation step. However, I do not think we should necessarily raise here. Ideally, since multiple attributes could have validation errors, there is a way to raise them all at once.

# and limitations under the License.

module AwsRecord
class GeneratedAttribute
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Guiding Question: How do we surface documentation about these options to users? How do we populate the "help" command?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make sure we are documenting options early and often.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rails will automatically display a USAGE file that you provide (I haven't started writing this, but a bulk of it will probably be cleaned and formatted snippets from the SEP)

# and limitations under the License.

require 'aws-record'
require 'generators/secondary_index'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should be able to just do require 'aws-record-generator' here. There should be something gem-level that consumes everything else.

@YashoSharma YashoSharma changed the title (WIP) Basic tests and parsing Basic tests and parsing Jun 4, 2018
Copy link
Contributor

@awood45 awood45 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good to merge, the remaining comments are stylistic but should be addressed in future changes.

t.write_capacity_units 4
"""

Then a "model" should be generated
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd do something here that makes the fixture chosen clear. I do see some step_definition magic, but could these steps themselves call out the fixture file?

@@ -0,0 +1,8 @@
require 'aws-record'

class TestModel1
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would give these fixtures meaningful names. Even "BasicGeneratedModel", and as your set of fixtures grows with the feature set, you will have an easier time keeping track.

def parse(field_definition)
name, type, opts = field_definition.split(':')
type = "string" if not type
type, opts = "string", type if OPTS.any? { |opt| type.include? opt }

opts = opts.split(',') if opts
type, opts = *parse_type_and_options(type, opts)
type, opts = *parse_type_and_options(name, type, opts)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe the * character is necessary here, Ruby will split the array without the splat if it is size two.

@YashoSharma YashoSharma merged commit 68598e4 into master Jun 4, 2018
@YashoSharma YashoSharma deleted the BasicTestsAndParsing branch June 4, 2018 21:26
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants