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
Conversation
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:
|
a9b8ab1
to
ab9f441
Compare
I'll start with some questions that come to mind:
|
test/scenarios/annotations/README.md
Outdated
export mode=all | ||
ktest ../katello/test/scenarios/scenario_test.rb | ||
unset mode | ||
``` |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 | ||
--- |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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: | ||
``` |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
1. How much time do these larger, functional tests add to our test
suite?
they added tests take 4.7 seconds on my box for:
Org create
Product create
repo create
repo sync
1. If they are not run with the unit tests, when would we run them?
And if they failed, what would we block on?
I think running as unit tests is preferred, its more that people may
become annoyed for having to re-record them frequently. The only other
place i think we'd run them is part of the release pipeline (but again i
think via unit tests is preferred).
1. We could separate them out and add a parallel CI test
1. 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.
I agree
1. How often do you think these annotations should be needing updates?
The annotations should only need to be updated if:
a) a new request is made during the scenarios
b) a request is no longer made that was being made during the scenarios
c) Some request that was being made no longer matches its old annotation
VCR cassettes will likely need to be re-recorded more often, any time some
parameter changes on any request during the recorded scenarios.
… |
test/scenarios/annotations/README.md
Outdated
|
||
## Matching requests | ||
|
||
The default matching request matching uses 'method' and 'path', but sometimes path is not enough. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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
test/support/scenario_support.rb
Outdated
end | ||
|
||
def create_org(org) | ||
#@org = org |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
org is org
test/support/scenario_support.rb
Outdated
require "active_support/concern" | ||
|
||
class ScenarioSupport | ||
attr_accessor(:user) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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') |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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| |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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 "" |
There was a problem hiding this comment.
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
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. |
I think i've addressed or commented on all your comments |
- method: get | ||
path: "/candlepin/environments/119c4753ff6d3b7bd0b76de6d5a5f94a" | ||
title: Retrieve candlepin environment | ||
description: Retrieve the environment object (TODO WHY?) |
There was a problem hiding this comment.
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?
@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:
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.
No description provided.