-
Notifications
You must be signed in to change notification settings - Fork 682
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
burtlo/cleaner cli formatter #1331
Conversation
I would like to also break these formatters out into separate files inside a formatters directory. But let's start with this large file and this huge change. |
I tried to clean up the entire CLI formatter by addressing what was happening in From there I wrap the control data into a class I call This is still pretty tricky code because you have:
|
@@ -63,7 +63,7 @@ def stop(notification) | |||
private | |||
|
|||
def format_example(example) | |||
if example.metadata[:description_args].length > 0 && !example.metadata[:skip].nil? | |||
if !example.metadata[:description_args].empty? && example.metadata[:skip] |
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 first statement was a suggestion by Rubocop. The second is the same way of expressing the same thing.
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.
Agreed on the first. For the second my personal opinion has always been to prefer clarity. It does require one more step in thinking. overall ok :)
@@ -92,7 +92,7 @@ def format_example(example) | |||
end | |||
|
|||
class InspecRspecJson < InspecRspecMiniJson # rubocop:disable Metrics/ClassLength | |||
RSpec::Core::Formatters.register self, :start, :stop, :dump_summary | |||
RSpec::Core::Formatters.register self, :stop, :dump_summary |
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 refactored the @profiles_info to a memozied helper method so I no longer need to use #start
.
# This class wraps a control hash object to provide a useful inteface for | ||
# maintaining the associated profile, ids, results, title, etc. | ||
# | ||
class Control |
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.
Don't love that I used the name Control
here but it is one within this context. I wouldn't want to confuse anyone here.
@burtlo This is changing a lot. Thank you for the hard work. Some comments I have:
|
@vjeffrey did the initial work during our hack day and then I jumped on board. When I pushed my latest changes on my branch I failed to find that pull request again and get that integrated. So yes this appends more changes on top of her work. @chris-rock I'll add the tests today and continue to rebase this branch. |
@burtlo Just rebase it on latest master, this enables us to see your individual contributions. |
This is the current state of
You may need to disregard my results here. I noticed that a lot of the tests are failing because I have some noisy gems.
I need to run I found a number of issues that I have addressed. Thank goodness for test suites. |
Given a control defined like this: control "tmp-1.0" do # A unique ID for this control
impact 0.7 # The criticality, if this control fails.
title "Create /tmp directory" # A human-readable title
desc "An optional description..." # Describe why this is needed
tag data: "temp data" # A tag allows you to associate key information
tag "security" # to the test
ref "Document A-12", url: 'http://...' # Additional references
describe file('/tmp') do # The actual test
it { should be_directory }
it { should_not be_directory }
end
end The results were ordered: fails + skips + passes. Generating the following:
My refactored formatter orders based on the examples position within the control. Generating the following:
Should I leave it as positioning or have them re-ordered based on previous ordering? |
@burtlo I like the new ordering, it is more natural for the user |
This is getting trickier by the minute. What should the output look like when a profile includes another profile like the one defined in
Because of the way the examples are processed and the way that the profiles are treated the output looks like the following at the moment:
The anonymous example is processed first and belongs to the I think the best course of action is to see if I can get the profile added to the list of profiles known to the formatter when the I assume the desired output would be:
Which means I need to update def load
all_controls = []
@target_profiles.each do |profile|
@test_collector.add_profile(profile)
write_lockfile(profile) if @create_lockfile
profile.locked_dependencies
# store the profile_context generated ...
profile_context = profile.load_libraries
# so that it can be used here to review the requirements
profile_context.dependencies.list.values.each do |requirement|
@test_collector.add_profile(requirement.profile)
end
@attributes |= profile.runner_context.attributes
all_controls += profile.collect_tests
end
all_controls.each do |rule|
register_rule(rule) unless rule.nil?
end
end |
I'm getting intermittent failures when I run
|
profile_context = profile.load_libraries | ||
|
||
profile_context.dependencies.list.values.each do |requirement| | ||
@test_collector.add_profile(requirement.profile) |
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.
To properly display the dependent profiles I needed to view the ProfileContext and the profiles within it. Is there a better way to grab the profiles that are required?
@@ -197,7 +197,7 @@ | |||
let(:out) { inspec('exec ' + simple_inheritance) } | |||
|
|||
it 'should print the profile information and then the test results' do | |||
out.stdout.force_encoding(Encoding::UTF_8).must_include "local://\n\n\n\e[38;5;9m × tmp-1.0: Create /tmp directory (1 failed)\e[0m\n\e[38;5;9m × File /tmp should not be directory\n" | |||
out.stdout.force_encoding(Encoding::UTF_8).must_include "\e[38;5;9m × tmp-1.0: Create /tmp directory (1 failed)\e[0m\n\e[38;5;41m ✔ File /tmp should be directory\e[0m\n\e[38;5;9m × File /tmp should not be directory\n" |
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 test was expecting errors + passes + skips. Now the format of the output is in the natural order of the control.
@@ -75,6 +75,7 @@ def format_example(example) | |||
|
|||
res = { | |||
id: example.metadata[:id], | |||
profile_id: example.metadata[:profile_id], |
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 makes it easier to get to the id of the profile instead of the previous process of trying to find it.
class Control | ||
include Comparable | ||
|
||
def <=>(other) |
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.
Comparable is implemented here not for sorting but for better comparison between controls to ensure that similar named controls in different profiles are considered and displayed separately.
Other than those intermittent errors and failures I feel like this content is tested with the existing tests and ready for some more review. |
I've seen this issue locally and it was fixed when I ran it again. https://travis-ci.org/chef/inspec/jobs/180257685#L631 |
Rebasing off of master this morning to keep it current. |
Rebasing off of master this morning to keep it current. This is a new error that seems related to the output of the formatter: https://travis-ci.org/chef/inspec/jobs/183295983#L686 I will take a look and address the issue. |
I've re-run this several times on my local machine, with the seed that showed the issue Would someone run these tests on their system with this seed or other random seeds and ensure that you see it all working. BTW I was getting serious errors when initially running the test because of some gems that needed cleaning that were gumming up the very brittle output that we were verifying against:
So I needed to do some gem cleaning: http://stackoverflow.com/questions/17936340/unresolved-specs-during-gemspecification-reset |
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.
Overall I love these huge improvements. There is a lot in here and some fantastic changes to readability and flow. Let's get a few comments covered. Huge kudos @burtlo !!
@@ -63,7 +63,7 @@ def stop(notification) | |||
private | |||
|
|||
def format_example(example) | |||
if example.metadata[:description_args].length > 0 && !example.metadata[:skip].nil? | |||
if !example.metadata[:description_args].empty? && example.metadata[:skip] |
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.
Agreed on the first. For the second my personal opinion has always been to prefer clarity. It does require one more step in thinking. overall ok :)
@control_tests.each do |control| | ||
all_unique_controls = Array(@all_controls).uniq | ||
|
||
all_unique_controls.each do |control| |
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.
just consistency: your next change further down doesn't assign the variable
Array(@all_controls).uniq.each do |control|
pick one 😁 ; variables are great for clarity and re-use, but may not be needed in this case
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 true. 👍
|
||
def examples_with_controls | ||
(examples - examples_without_controls) | ||
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.
Nice section overall, gz 👍
@@ -105,50 +106,36 @@ def initialize(*args) | |||
@backend = nil | |||
end | |||
|
|||
attr_reader :profiles |
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.
Do we need to expose profiles? If not imho it's great to leave it private. What do you think?
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're right it doesn't need to be public. The methods associated with the interface for formatter and #add_profile
are likely the only things that need to be public. Will definitely make the change and make more things private
.
def example2profile(example, profiles) | ||
profiles.find { |p| profile_contains_example?(p, example) } | ||
def examples | ||
@examples ||= @output_hash.delete(:controls) |
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 really like caching personally. In this example I'm not sure about when it is called. What do you think about assigning examples when they naturally need to be in the code flow?
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 is set through in InspecRspecJson#stop
when it is called through InspecRspecJson#examples_without_controls
/ InspecRspecJson#examples_with_controls
. I've didn't like the @output_hash.delete(:controls)
so I've changed it to retrieve the value from the hash, no longer storing the value. So the method remains as a way to alias of @output_hash[:controls]
to examples
.
The full JSON formatter was using the start step to setup the profiles_info. I moved that to a memozied method so that the first time it is called it will be created. Signed-off-by: Franklin Webber <[email protected]>
Cleans up the #stop action on the JSON formatter by creating more methods that memoize values or provide values through a method interface. There is still more that can be done with the whole mapping examples to controls through profiles. Signed-off-by: Franklin Webber <[email protected]>
A lot of the work in #flush_current_control is acting on the control. I am starting the flip of the control and bringing those messages being sent originating from a control class itself. Signed-off-by: Franklin Webber <[email protected]>
The profiles will display the controls with their results and then display the examples not associated with any control but within the profile. Signed-off-by: Franklin Webber <[email protected]>
* Fixes an issue when specifying no profile * Fixes an issue when displaying a profile that has included/required profiels * Fixes an issue when specifying profiles with only metadata * Fixes formatting for spacing to ensure it adheres to previous alignment * Fixes issue with the Control object and the rolling up of failed and skipped examples. Signed-off-by: Franklin Webber <[email protected]>
* Moved things around for better understanding of the class * Used `private` to denote what was on the public interface * Solved the ugly TODO which was calculating the state of the control's summary * Used `#examples` instead of `res = control[:results]` throughout the #summary and #title methods Signed-off-by: Franklin Webber <[email protected]>
The class size is too big and Rubocop is right. There are a few more classes in there that could be extracted but I am going to ignore it. The other issues that it presented were fair. Signed-off-by: Franklin Webber <[email protected]>
Based on some feedback from @arlimus there were some methods that were not part of the public inteface that I moved to private. I changed the examples collection from a delete from the output_hash to retrieve the controls. Created a helper for the all_unique_controls which was used in two helper methods. Signed-off-by: Franklin Webber <[email protected]>
The profiles method was never public and the @profiles is clearer. Signed-off-by: Franklin Webber <[email protected]>
Rebasing off current chef/inspec:master |
Thank you so much for the huge improvement @burtlo and for all the continuous fixes you have added to get this merged!! 🎉 👍 😀 |
Updates RSpec CLI Formater to print profiles correctly
The profiles will display the controls with their results and then display the examples not associated with any control but within the profile.