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

Refactoring for testing #18

Open
wants to merge 41 commits into
base: master
Choose a base branch
from
Open

Refactoring for testing #18

wants to merge 41 commits into from

Conversation

exhuma
Copy link
Contributor

@exhuma exhuma commented May 3, 2017

This is a large PR. Sorry for the size but I wanted to submit something coherent. I'll try to add line-comments here on GitHub once I created the PR. I hope it's possible.

Most of the code is adding unit-tests. I tried to refrain from making changes to the codes, but there are some exceptions:

  • A few places contained bugs and I had to fix them in order to get the tests to pass.
  • Some minor refactoring was necessary in order to make testing possible (or easier).
  • The shell script is gone and has been replaced with "entry-points". This has several advantages:
    • The application can be bundled and install using the standard python method (pip, setup.py, ...).
    • Dependencies can be resolved automatically. It's no longer need to bundle third party libraries.
  • CLI Argument parsing has been split up for the different commands. This separates the commands into separate modules, and the main executable almost builds itself.

I've done my best to keep the commit messages helpful and make atomic commits so it should be a bit easier to follow.

If you have any questions, feel free to ask.

exhuma added 30 commits March 16, 2017 08:09
This prepares the overall code structure for entry points including
removal of the shell script. Managing the cron-script is not functional
yet. Nothing has been tested either. Committing so I can sync with a box
where I can test.
Passing script-path is also no longer required.
No longer need the "vendor" copy
Should play niced with virtualenvs (removes the risk of a name-clash).
Having the CRON commands in a separate module reduces the "contact area"
between our application and the external dependency. This makes both
testing and replacing the dependency easier in the future.
These tests surfaced two bugs which have been fixed with this commit as
well.
It was running under the namespace "InfluxdbClient" yet it had nothing
to do with it. This makes it more veratile and removed a dependency on
InfluxdbClient from `grafana.py`
Using a string literal here leads to incorrect exception messages when
the key "group_fields" does not exist (or any other exception on the
preceding line). Using a proper code-comment remedies this.
@@ -1,33 +0,0 @@
#!/usr/bin/env bash
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This shell shript has been replaced with munininfluxdb/main.py

@@ -0,0 +1,110 @@
from __future__ import print_function
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is one of the subcommands of the main command. It is loaded in munininfluxdb/main.py. In this particular case, it contains the cron code which I extracted from the other files. This way, the cron installation and deinstallation can be run separately (as also mentioned in the readme file).

@@ -0,0 +1,80 @@
import logging
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is one of the subcommands of the main command. It is loaded in munininfluxdb/main.py.

@@ -1,31 +1,22 @@
#!/usr/bin/env python
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is one of the subcommands of the main command. It is loaded in munininfluxdb/main.py.


try:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Before touching munin-influxdb I also sent a PR for storable which makes the module available on pypi. This has been accepted and we can install it via setup.py dependencies. The vendor folder is no longer needed.

try:
pwd.getpwnam('munin')
except KeyError:
CRON_USER = 'root'
Copy link
Contributor Author

Choose a reason for hiding this comment

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

cron has been extracted to a separate sub-command. So all cron related stuff has been removed from this file and moved to commands/cron.py

@@ -108,22 +91,15 @@ def main(args):
print("Then we're good! Have a nice day!")


if __name__ == "__main__":
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The "main" part of the commands is handled in munininfluxdb/main.py

@@ -112,7 +111,7 @@ def process_graph_types(self, fields):
if hasArea:
self.fill = 5
self.linewidth = 0
if hasArea:
if hasStack:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was a bug which surfaced through the unit-tests.

@@ -381,7 +380,7 @@ def create_dashboard(self, dashboardJson):
r.raise_for_status()


if __name__ == "__main__":
if __name__ == "__main__": # pragma: no cover
Copy link
Contributor Author

Choose a reason for hiding this comment

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

In case you have not worked with the coverage module yet: pragma: no cover prevents lines from being checked for coverage. There were some cases where this made sense to add (lines which are deliberately left out from unit-testing).

self.client, self.valid = None, False
if not silent:
print(" {0} Could not connect to database: {1}".format(Symbol.WARN_YELLOW, e))
except Exception as e:
LOG.debug(str(e), exc_info=True)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not logging anything made debugging very difficult here. This helps a bit.

@@ -107,10 +112,6 @@ def list_columns(self, series="/.*/"):

return res

@staticmethod
Copy link
Contributor Author

Choose a reason for hiding this comment

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

extracted for easier mocking/patching.

| ... | | | | |
+----------------------+-------+----------+----------+-----------+
"""
# In "group_fields" mode, all fields of a same plugin (ex: system, user, nice, idle... of CPU usage)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Replaced the string-literal with comments. Otherwise it's included in coverage which does not make sense.

import os
import sys
import pprint

from utils import ProgressBar, Symbol
from settings import Settings

from vendor import storable
import storable
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Again, storable is now available in pypi and has been added to the setup.py dependencies.


def discover_from_datafile(settings):
DataFileLine = namedtuple(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just a helper to make the code more readable (less use of indices).

'plugin value domain host field property')


def parse_datafile_line(line):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Extracted this block of code as separate function which allows us to test it separately.

return DataFileLine(plugin, value, domain, host, field, property)


def populate_settings(settings, datafile):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sane thing. Makes testing easier.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant