Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refs #18921,#18922 - scenario tests and annotation #6716

Merged
merged 1 commit into from May 7, 2017

Conversation

jlsherrill
Copy link
Member

No description provided.

@jlsherrill
Copy link
Member Author

jlsherrill commented Mar 30, 2017

Note this requires #6717 (Commit currently included here)

Lots of explanation here: https://github.com/Katello/katello/pull/6716/files#diff-0491b37365f9bbacc3357d8b9442422e

and a PR opening adding the first two workflows is here: theforeman/theforeman.org#845

A few questions arise from this PR, as this gives us the ability to do larger functional testing as part of our unit tests and maintain these annotations at the PR level:

  • Should we run these scenario tests as part of the normal unit tests against PRs (thats how the PR is now) Likely people would need to re-record more often
  • Should we add the 'rake katello:find_unannotated_requests' to the CI on unit tests? This would fail any PR that adds a request to the annotated workflows but does not update the annotations. We could instead add this to the nightly release job, so it doesn't fail the pr, but does fail the nightly.

@ehelms
Copy link
Member

ehelms commented Apr 4, 2017

I'll start with some questions that come to mind:

  1. How much time do these larger, functional tests add to our test suite?
  2. If they are not run with the unit tests, when would we run them? And if they failed, what would we block on?
  3. We could separate them out and add a parallel CI test
  4. I tend towards catching things as soon as possible since the question becomes whose responsibility is it when things break. If we do it in the nightly release testing, then the bats nanny is in charge.
  5. How often do you think these annotations should be needing updates?

export mode=all
ktest ../katello/test/scenarios/scenario_test.rb
unset mode
```
Copy link
Member

Choose a reason for hiding this comment

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

Be great if we could capture this in a rake task or script or playbook.

Copy link
Member Author

Choose a reason for hiding this comment

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

yeah, i can do that

version: nightly
foreman_version: nightly
script: osmenu.js
---
Copy link
Member

Choose a reason for hiding this comment

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

This looks like a Jekyll template, I'm just not clear on what it is doing here.

Copy link
Member Author

Choose a reason for hiding this comment

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

The purpose is to be published on theforeman.org, so this is adding the page header in there for that.

path: "/some/path"
title:
description:
```
Copy link
Member

Choose a reason for hiding this comment

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

If the current tooling can generate this information for us, why do we need to store it in git?

Copy link
Member Author

Choose a reason for hiding this comment

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

It generates default matchers, but:
a) doesn't know the title and description
b) sometimes the default matching mechanism (path and method) aren't enough and you have to use something like 'starts_with' for more complex matching.

@jlsherrill
Copy link
Member Author

jlsherrill commented Apr 4, 2017 via email


## Matching requests

The default matching request matching uses 'method' and 'path', but sometimes path is not enough.
Copy link
Member

Choose a reason for hiding this comment

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

This part is not entirely clear to me reading through, maybe an example would help with respect to where and how I add this to an anotation.

title:
description:
```
Simply copy and paste that into your annotation yaml file. Edit, adding a description and title.
Copy link
Member

Choose a reason for hiding this comment

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

Are description and title required? If yes, can you add something that as part of checking for unannotated requests also "lints" the annotations to ensure these are filled out for each?

Copy link
Member Author

Choose a reason for hiding this comment

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

Title is required, description is not (since some requests are quite obvious). I've added a check to check for blank titles

Run the rake task to generate documentation:

```
rake katello:create_annotated_output
Copy link
Member

Choose a reason for hiding this comment

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

The idea is this output gets copied to theforeman.org I take it? How often would this be done? Should we fail somewhere in the process to ensure the docs stay up to date?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is is up for debate, but i think it should be the release nanny's duties. I don't think this needs to be done nightly or even weekly, but ideally would be updated with each release

Copy link
Member

Choose a reason for hiding this comment

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

So there would be a roughly 3 month gap between annotations. Would bleeding edge or newly added ones be more useful to Pulp and Candlepin or do we expect them to really just look at stable releases?

Copy link
Member Author

Choose a reason for hiding this comment

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

I expect them to more look at stable releases when helping debug issues. I might could see generating new documentation when large features are added to run by them, but I don't know how often that would even be relevant.

@@ -0,0 +1,77 @@
# Annotated Requests
Copy link
Member

Choose a reason for hiding this comment

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

Could we link this from the top level README or somewhere else that is typically seen by a developer? The content living here is fine since its relative but I want to ensure it's easy to find for developers.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

title: Annotated Requests
version: nightly
foreman_version: nightly
script: osmenu.js
Copy link
Member

Choose a reason for hiding this comment

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

I wasn't seeing where this script was being used

Copy link
Member Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Specifically this osmenu.js script (not the erb template).

Copy link
Member Author

Choose a reason for hiding this comment

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

oh i see what you mean

end

def create_org(org)
#@org = org
Copy link
Member

Choose a reason for hiding this comment

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

org is org

require "active_support/concern"

class ScenarioSupport
attr_accessor(:user)
Copy link
Member

Choose a reason for hiding this comment

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

Never seen attr_accessor defined with parens.

@@ -0,0 +1,34 @@
module Annotations
class VcrRequest
attr_accessor :method, :path, :request_body, :response_body, :response_code
Copy link
Member

Choose a reason for hiding this comment

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

Minor point, but looking at the code I think most if not all of these could just be attr_reader for outside access.

class VcrRequest
attr_accessor :method, :path, :request_body, :response_body, :response_code

def initialize(hash)
Copy link
Member

Choose a reason for hiding this comment

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

Consider using fetch on the hash to ensure each of the values below is defined.

vcr_request.request_body.include?(request_body)
else
true
end
Copy link
Member

Choose a reason for hiding this comment

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

Consider reducing this to use a guard clause

self.path = vcr_request.path
end
requests << vcr_request if matched
end
Copy link
Member

Choose a reason for hiding this comment

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

You could consider breaking this up into a series of selects pairing down the list in a sequence of steps. This could make any future debugging of this earlier as you can check the set of requests after each select to figure out where a particular request was removed from the selection.

Copy link
Member Author

Choose a reason for hiding this comment

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

i feel like using .selects() wouldn't work well for these conditions. Not to say this couldn't be cleaned up some

Copy link
Member

Choose a reason for hiding this comment

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

Perhaps, but as you can see thats me trying to read and interpret it. That each condition is skipping over an item in the list (i.e. reducing the list of items that ultimate action should be taken on).

vcr_requests.each do |request|
found << request if self.matches?(request)
end
found
Copy link
Member

Choose a reason for hiding this comment

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

You could reduce this to a select

def match_requests(vcr_requests)
found = []
vcr_requests.each do |request|
found << request if self.matches?(request)
Copy link
Member

Choose a reason for hiding this comment

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

I am not seeing where the method matches? is defined.

Copy link
Member Author

Choose a reason for hiding this comment

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

ah this is no longer used (was from a previous iteration)

module Annotations
class MatchedAnnotation
attr_accessor :annotation, :requests, :method, :path, :description, :title,
:ignore_duplicates, :starts_with, :ignored_count, :request_body
Copy link
Member

Choose a reason for hiding this comment

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

Something to think about, if not all of these are being set outside this class they could be reduced to attr_reader and further reduced to just using @ if they aren't being read outside the class. This would reduce the "interface" surface of the class.

File.read(File.dirname(__FILE__) + '/templates/annotation.erb')
end

def initialize(filename)
Copy link
Member

Choose a reason for hiding this comment

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

Maybe move this to the top since its the entry for the class

end

def initialize(filename)
@file = File.open(filename, 'w')
Copy link
Member

Choose a reason for hiding this comment

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

Maybe add a safety check to ensure the file exists before opening?

Copy link
Member Author

Choose a reason for hiding this comment

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

and do what if it doesn't ? something other than raise an exception?


def close
@file.close
end
Copy link
Member

Choose a reason for hiding this comment

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

Feels dangerous to have to explicitly remember to call to close the file. I am guessing you are doing this to be able to append to the file? And you want to do that in memory rather than writing a function that opens, writes in append mode and closes?

Copy link
Member Author

Choose a reason for hiding this comment

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

You have to 'remember' to do that when working with the normal file object. I would prefer this method rather than continually opening the file.

Copy link
Member

Choose a reason for hiding this comment

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

If you use a block with the File.open method you don't have to remember anything :)

output = "# #{name}\n"
matched_annotations.each do |annotation|
output += ERB.new(annotation_template).result(binding)
count += 1
Copy link
Member

Choose a reason for hiding this comment

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

It looks like this count is arbitrarily being incremented. Where is it being used?

end

def self.load_annotations(directory)
Dir["#{directory}/*.yaml"].map do |file|
Copy link
Member

Choose a reason for hiding this comment

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

Could be useful to check if the directory exists and throw an error when trying to debug.

Copy link
Member Author

Choose a reason for hiding this comment

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

the directory is defined in git, i don't think its not going to exist unless for some reason you think the user will delete it by mistake?

Copy link
Member

Choose a reason for hiding this comment

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

I try not to predict user behavior. I am just thinking that if this produces an empty set (for some reason) you might chase the rabbit before realizing it was just the directory it expected didn't exist.

@@ -0,0 +1,35 @@
module Annotations
Copy link
Member

Choose a reason for hiding this comment

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

Not that I expect us to add (but you never know what a plugin may do) a gem by this name, but there are some in existence and I could see one coming along and being used. Call it future proofing but you may consider wrapping this in module Katello.

Copy link
Member Author

Choose a reason for hiding this comment

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

👍

CASSETTE_DIR = "#{Katello::Engine.root}/test/fixtures/vcr_cassettes/scenarios/".freeze
OUTPUT_DIR = "#{Rails.root}/tmp/".freeze

desc ""
Copy link
Member

Choose a reason for hiding this comment

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

Were you intending to have a description? Makes it easier to find if you forget the name with rake -T

@ehelms
Copy link
Member

ehelms commented Apr 4, 2017

Based on your responses, seems like this should only change if a user is editing tests and such where it would affect the annotations (and not say on a random UI PR fix) so I lean towards running this with normal unit tests and blocking a PR if the annotations are out of date.

@jlsherrill
Copy link
Member Author

I think i've addressed or commented on all your comments

@bbuckingham bbuckingham self-assigned this Apr 24, 2017
- method: get
path: "/candlepin/environments/119c4753ff6d3b7bd0b76de6d5a5f94a"
title: Retrieve candlepin environment
description: Retrieve the environment object (TODO WHY?)
Copy link
Member

Choose a reason for hiding this comment

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

Any update on the TODO?

@bbuckingham
Copy link
Member

@jlsherrill, nice work! ACK to the approach/discussion and we can evolve the process, if needed, as we begin using it.

If possible, it may be good to summarize here or email info such as:

  • expectation/impact for developers
  • specify when they will be run (e.g. during UT)

Basically, give the devs a heads-up, since they may not all be following the PR. :)

This adds a handful of scenario tests, that is testing
full stack integration by executing dynflow actions
and recording their results in VCR.  Only a few scenarios
are tested so far, but more can be easily added.

In addition this adds tooling to generat annotated
documentation around these workflows and requests.

See test/scenarios/annotations/README.md for more
info.
@jlsherrill jlsherrill merged commit fdc3b7b into Katello:master May 7, 2017
@jlsherrill jlsherrill deleted the scenarios branch May 7, 2017 01:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
4 participants