├── .gitignore
├── CHANGELOG.rst
├── LICENSE
├── MANIFEST.in
├── README.rst
├── django_jenkins
├── __init__.py
├── apps.py
├── management
│ ├── __init__.py
│ └── commands
│ │ ├── __init__.py
│ │ └── jenkins.py
├── models.py
├── runner.py
└── tasks
│ ├── __init__.py
│ ├── pylint.rc
│ ├── run_flake8.py
│ ├── run_pep8.py
│ ├── run_pyflakes.py
│ ├── run_pylint.py
│ └── with_coverage.py
├── setup.cfg
├── setup.py
├── tests
├── __init__.py
├── manage.py
├── runner.py
├── settings.py
├── static
│ ├── css
│ │ ├── test.css
│ │ └── test.scss
│ └── js
│ │ └── test.js
├── test_app
│ ├── __init__.py
│ ├── features
│ │ ├── example.feature
│ │ └── example_steps.py
│ ├── migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── models.py
│ ├── not_for_coverage
│ │ ├── __init__.py
│ │ ├── one.py
│ │ └── two.py
│ ├── south_migrations
│ │ ├── 0001_initial.py
│ │ └── __init__.py
│ ├── static
│ │ ├── css
│ │ │ └── test_errors.css
│ │ └── js
│ │ │ └── test.js
│ ├── templates
│ │ ├── 404.html
│ │ ├── 500.html
│ │ └── test_app
│ │ │ └── wm_test_click.html
│ ├── tests.py
│ ├── urls.py
│ └── views.py
├── test_app_dirs
│ ├── __init__.py
│ ├── models
│ │ ├── __init__.py
│ │ └── sample.py
│ ├── not_for_coverage
│ │ ├── __init__.py
│ │ ├── one.py
│ │ └── two.py
│ └── tests
│ │ ├── __init__.py
│ │ └── test_discovery_dir_tests.py
└── test_non_bound.py
└── tox.ini
/.gitignore:
--------------------------------------------------------------------------------
1 | .cache
2 | .ve2?
3 | .ve3?
4 | .coverage
5 | *.pyc
6 | dist
7 | reports
8 | .reports_py??
9 | .tox
10 | django_jenkins.egg-info/
11 | MANIFEST
12 | geckodriver.log
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/CHANGELOG.rst:
--------------------------------------------------------------------------------
1 | 0.110.0 2016-09-15
2 | ~~~~~~~~~~~~~~~~~~
3 |
4 | * Flake8 >= 3.0 support
5 | * `scss-lint` task added
6 |
7 | 0.19.0 2016-06-15
8 | ~~~~~~~~~~~~~~~~~
9 |
10 | * Flake8 >= 2.5.0 support
11 | * Drop django 1.7 support
12 | * Tested on django 1.10
13 | * Add suppport for `.add_arguments` from custom test runner for `jenkins` command
14 |
15 | 0.18.0 1985-10-26
16 | ~~~~~~~~~~~~~~~~~
17 |
18 | * An exceptional release for the last 5 years issued not on 15th day of a month
19 | * Drop python 2.6 support
20 | * Drop django 1.6 support
21 | * Add django 1.9 compatibility
22 | * Drop scss-lint support (the tool no longer have xml output)
23 |
24 | 0.17.0 2015-04-15
25 | ~~~~~~~~~~~~~~~~~~
26 |
27 | * Django 1.8 compatibility
28 | * Added support for excluding paths in the pyflakes runner
29 | * --coverage-html-report option removed
30 | * --coverage-format option added
31 |
32 | 0.16.4 2014-12-15
33 | ~~~~~~~~~~~~~~~~~
34 |
35 | * New scss-lint task
36 | * Added support for pylint plugins
37 | * Include STATICFILES_DIRS to search path for csslint and jshint
38 | * Search pep8 config in setup.cfg and tox.ini.
39 | * Fix non-ascii tracback problem
40 | * Fix error handlin in case exception happens in fixture loading
41 | * Fix max-complexity option overriding
42 |
43 | 0.16.3 2014-08-15
44 | ~~~~~~~~~~~~~~~~~
45 |
46 | * Python 2.6 compatibility returned
47 | * Added `--project-apps-tests` options to limit tests discovery by `PROJECT_APPS` setting value
48 | * Fix coverage for apps with separate models packages under django 1.6
49 | * Fix missing pep8 excludes option from pep8 config
50 |
51 |
52 | 0.16.0 2014-07-15
53 | ~~~~~~~~~~~~~~~~~
54 |
55 | * Django 1.7 compatibility
56 | * Support for all standard django test runner options
57 | * Migrations now our friends and checked by all linters, `south_migrations` are ignored
58 | * ``django_jenkins.tasks.with_coverage`` depricated, use command line option instead `./manage.py jenkins --enable-coverage`
59 | * ``django_jenkins.tasks.run_graphmodels`` removed
60 | * ``django_jenkins.tasks.with_local_celery`` removed
61 | * Ability to run linters (pep8/pyflakes/pylint/csslint/jshint) from command line without tests removed (feel free to PR it back, if you need them)
62 |
63 |
64 | 0.15.0 2014-02-15
65 | ~~~~~~~~~~~~~~~~~
66 |
67 | * Speed up and reduced memory usage for junit reports generation
68 | * django_tests and dir_tests test discovery tasks are replaced by directory discover test runner build-in in django 1.6
69 | * Removed unmaintained lettuce tests support
70 | * Removed unmaintained behave tests support
71 | * Fixed non-asci support in junit reports
72 |
73 |
74 | 0.14.1 2013-08-15
75 | ~~~~~~~~~~~~~~~~~
76 |
77 | * Django 1.6 compatibility
78 | * Flake8 support
79 | * Pep8 file configuration support
80 | * CSSLint no longer shipped with django-jenkins. Install it with ``npm install csslint -g``
81 |
82 |
83 | 0.14.0 2012-12-15
84 | ~~~~~~~~~~~~~~~~~
85 |
86 | * Python 3 (with django 1.5) support
87 | * JSHint no longer shipped with django-jenkins. Install it with ``npm install jshint -g``
88 |
89 |
90 | 0.13.0 2012-07-15
91 | ~~~~~~~~~~~~~~~~~
92 |
93 | * unittest2 compatibility
94 | * **WARNING:** Junit test data now stored in one junit.xml file
95 | * Support for pep8 1.3
96 | * New in-directory test discovery task
97 | * Added --liveserver option
98 | * Fixes in jslint and csslint tasks
99 |
100 | 0.12.1 2012-03-15
101 | ~~~~~~~~~~~~~~~~~
102 |
103 | * Added Celery task
104 | * Add nodejs support for jslint and csslint tasks
105 | * Improve js and css files selection
106 | * Bug fixes
107 |
108 | 0.12.0 2012-01-15
109 | ~~~~~~~~~~~~~~~~~
110 |
111 | * Django 1.3 in requirements
112 | * Windmill support was removed (Django 1.4 has a better implementation)
113 | * Ignore South migrations by default
114 | * Added SLOCCount task
115 | * Added Lettuce testing task
116 | * Added CSS Lint task
117 | * Used xml output format for jslint
118 | * Used native pep8 output format
119 |
120 | 0.11.1 2010-06-15
121 | ~~~~~~~~~~~~~~~~~
122 |
123 | * Do not produce file reports for jtest command by default
124 | * Ignore Django apps without models.py file, as in Django test command
125 | * Fix jslint_runner.js packaging
126 | * Fix coverage file filtering
127 |
128 | 0.11.0 2010-04-15
129 | ~~~~~~~~~~~~~~~~~
130 |
131 | * Support pep8, Pyflakes, jslint tools
132 | * Added jtest command
133 | * Allow specify custom test runner
134 | * Various fixes, thnk githubbers :)
135 |
136 | 0.10.0 2010-02-15
137 | ~~~~~~~~~~~~~~~~~
138 |
139 | * Pluggable ci tasks refactoring
140 | * Alpha support for windmill tests
141 | * Partial python 2.4 compatibility
142 | * Renamed to django-jenkins
143 |
144 | 0.9.1 2010-12-15
145 | ~~~~~~~~~~~~~~~~
146 |
147 | * Python 2.5 compatibility
148 | * Make compatible with latest Pylint only
149 |
150 | 0.9.0 2010-10-15
151 | ~~~~~~~~~~~~~~~~
152 |
153 | * Initial public release
154 |
155 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU LESSER GENERAL PUBLIC LICENSE
2 | Version 3, 29 June 2007
3 |
4 | Copyright (C) 2007 Free Software Foundation, Inc.
5 | Everyone is permitted to copy and distribute verbatim copies
6 | of this license document, but changing it is not allowed.
7 |
8 |
9 | This version of the GNU Lesser General Public License incorporates
10 | the terms and conditions of version 3 of the GNU General Public
11 | License, supplemented by the additional permissions listed below.
12 |
13 | 0. Additional Definitions.
14 |
15 | As used herein, "this License" refers to version 3 of the GNU Lesser
16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU
17 | General Public License.
18 |
19 | "The Library" refers to a covered work governed by this License,
20 | other than an Application or a Combined Work as defined below.
21 |
22 | An "Application" is any work that makes use of an interface provided
23 | by the Library, but which is not otherwise based on the Library.
24 | Defining a subclass of a class defined by the Library is deemed a mode
25 | of using an interface provided by the Library.
26 |
27 | A "Combined Work" is a work produced by combining or linking an
28 | Application with the Library. The particular version of the Library
29 | with which the Combined Work was made is also called the "Linked
30 | Version".
31 |
32 | The "Minimal Corresponding Source" for a Combined Work means the
33 | Corresponding Source for the Combined Work, excluding any source code
34 | for portions of the Combined Work that, considered in isolation, are
35 | based on the Application, and not on the Linked Version.
36 |
37 | The "Corresponding Application Code" for a Combined Work means the
38 | object code and/or source code for the Application, including any data
39 | and utility programs needed for reproducing the Combined Work from the
40 | Application, but excluding the System Libraries of the Combined Work.
41 |
42 | 1. Exception to Section 3 of the GNU GPL.
43 |
44 | You may convey a covered work under sections 3 and 4 of this License
45 | without being bound by section 3 of the GNU GPL.
46 |
47 | 2. Conveying Modified Versions.
48 |
49 | If you modify a copy of the Library, and, in your modifications, a
50 | facility refers to a function or data to be supplied by an Application
51 | that uses the facility (other than as an argument passed when the
52 | facility is invoked), then you may convey a copy of the modified
53 | version:
54 |
55 | a) under this License, provided that you make a good faith effort to
56 | ensure that, in the event an Application does not supply the
57 | function or data, the facility still operates, and performs
58 | whatever part of its purpose remains meaningful, or
59 |
60 | b) under the GNU GPL, with none of the additional permissions of
61 | this License applicable to that copy.
62 |
63 | 3. Object Code Incorporating Material from Library Header Files.
64 |
65 | The object code form of an Application may incorporate material from
66 | a header file that is part of the Library. You may convey such object
67 | code under terms of your choice, provided that, if the incorporated
68 | material is not limited to numerical parameters, data structure
69 | layouts and accessors, or small macros, inline functions and templates
70 | (ten or fewer lines in length), you do both of the following:
71 |
72 | a) Give prominent notice with each copy of the object code that the
73 | Library is used in it and that the Library and its use are
74 | covered by this License.
75 |
76 | b) Accompany the object code with a copy of the GNU GPL and this license
77 | document.
78 |
79 | 4. Combined Works.
80 |
81 | You may convey a Combined Work under terms of your choice that,
82 | taken together, effectively do not restrict modification of the
83 | portions of the Library contained in the Combined Work and reverse
84 | engineering for debugging such modifications, if you also do each of
85 | the following:
86 |
87 | a) Give prominent notice with each copy of the Combined Work that
88 | the Library is used in it and that the Library and its use are
89 | covered by this License.
90 |
91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license
92 | document.
93 |
94 | c) For a Combined Work that displays copyright notices during
95 | execution, include the copyright notice for the Library among
96 | these notices, as well as a reference directing the user to the
97 | copies of the GNU GPL and this license document.
98 |
99 | d) Do one of the following:
100 |
101 | 0) Convey the Minimal Corresponding Source under the terms of this
102 | License, and the Corresponding Application Code in a form
103 | suitable for, and under terms that permit, the user to
104 | recombine or relink the Application with a modified version of
105 | the Linked Version to produce a modified Combined Work, in the
106 | manner specified by section 6 of the GNU GPL for conveying
107 | Corresponding Source.
108 |
109 | 1) Use a suitable shared library mechanism for linking with the
110 | Library. A suitable mechanism is one that (a) uses at run time
111 | a copy of the Library already present on the user's computer
112 | system, and (b) will operate properly with a modified version
113 | of the Library that is interface-compatible with the Linked
114 | Version.
115 |
116 | e) Provide Installation Information, but only if you would otherwise
117 | be required to provide such information under section 6 of the
118 | GNU GPL, and only to the extent that such information is
119 | necessary to install and execute a modified version of the
120 | Combined Work produced by recombining or relinking the
121 | Application with a modified version of the Linked Version. (If
122 | you use option 4d0, the Installation Information must accompany
123 | the Minimal Corresponding Source and Corresponding Application
124 | Code. If you use option 4d1, you must provide the Installation
125 | Information in the manner specified by section 6 of the GNU GPL
126 | for conveying Corresponding Source.)
127 |
128 | 5. Combined Libraries.
129 |
130 | You may place library facilities that are a work based on the
131 | Library side by side in a single library together with other library
132 | facilities that are not Applications and are not covered by this
133 | License, and convey such a combined library under terms of your
134 | choice, if you do both of the following:
135 |
136 | a) Accompany the combined library with a copy of the same work based
137 | on the Library, uncombined with any other library facilities,
138 | conveyed under the terms of this License.
139 |
140 | b) Give prominent notice with the combined library that part of it
141 | is a work based on the Library, and explaining where to find the
142 | accompanying uncombined form of the same work.
143 |
144 | 6. Revised Versions of the GNU Lesser General Public License.
145 |
146 | The Free Software Foundation may publish revised and/or new versions
147 | of the GNU Lesser General Public License from time to time. Such new
148 | versions will be similar in spirit to the present version, but may
149 | differ in detail to address new problems or concerns.
150 |
151 | Each version is given a distinguishing version number. If the
152 | Library as you received it specifies that a certain numbered version
153 | of the GNU Lesser General Public License "or any later version"
154 | applies to it, you have the option of following the terms and
155 | conditions either of that published version or of any later version
156 | published by the Free Software Foundation. If the Library as you
157 | received it does not specify a version number of the GNU Lesser
158 | General Public License, you may choose any version of the GNU Lesser
159 | General Public License ever published by the Free Software Foundation.
160 |
161 | If the Library as you received it specifies that a proxy can decide
162 | whether future versions of the GNU Lesser General Public License shall
163 | apply, that proxy's public statement of acceptance of any version is
164 | permanent authorization for you to choose that version for the
165 | Library.
166 |
167 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | include LICENSE
2 | include README.rst
3 | include MANIFEST.in
4 | include django_jenkins/tasks/pylint.rc
5 | recursive-include tests *
6 | global-exclude __pycache__
7 | global-exclude *.pyc
8 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | django-jenkins
2 | ==============
3 |
4 | Plug and play continuous integration with Django and Jenkins
5 |
6 | .. image:: https://img.shields.io/pypi/v/django-jenkins.svg
7 | :target: https://pypi.python.org/pypi/django-jenkins
8 |
9 | .. image:: https://requires.io/github/kmmbvnr/django-jenkins/requirements.png?branch=master
10 | :target: https://requires.io/github/kmmbvnr/django-jenkins/requirements/?branch=master
11 |
12 | .. image:: https://badges.gitter.im/Join%20Chat.svg
13 | :target: https://gitter.im/kmmbvnr/django-jenkins?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
14 |
15 |
16 | Installation
17 | ------------
18 |
19 | From PyPI::
20 |
21 | $ pip install django-jenkins
22 |
23 | Or by downloading the source and running::
24 |
25 | $ python setup.py install
26 |
27 | Latest git version::
28 |
29 | $ pip install -e git+git://github.com/kmmbvnr/django-jenkins.git#egg=django-jenkins
30 | $ pip install coverage
31 |
32 | Installation for Python 3::
33 |
34 | Works out of the box
35 |
36 | Usage
37 | -----
38 |
39 | Add ``'django_jenkins'`` to your ``INSTALLED_APPS`` list.
40 | Configure Jenkins to run the following command::
41 |
42 | $ ./manage.py jenkins --enable-coverage
43 |
44 | This will create reports/ directory with junit xml, Coverage and Pylint
45 | reports.
46 |
47 | For more details see the generic tutorial: https://sites.google.com/site/kmmbvnr/home/django-jenkins-tutorial
48 |
49 | Settings
50 | --------
51 |
52 | - ``PROJECT_APPS``
53 |
54 | If present, it is supposed to be a list/tuple of django apps for Jenkins to run.
55 | Tests, reports, and coverage are generated only for the apps from this list.
56 |
57 | - ``JENKINS_TASKS``
58 |
59 | List of Jenkins reporters executed by ``./manage.py jenkins`` command.
60 |
61 | Default value::
62 |
63 | JENKINS_TASKS = ()
64 |
65 | - ``JENKINS_TEST_RUNNER``
66 |
67 | The name of the class to use for starting the test suite for ``jenkins`` command.
68 | Class should be inherited from
69 | ``django_jenkins.runner.CITestSuiteRunner``
70 |
71 |
72 | Reporters
73 | ---------
74 |
75 | Here is the reporters prebuild with django-jenkins
76 |
77 | - ``django_jenkins.tasks.run_pylint``
78 |
79 | Runs Pylint_ over selected Django apps.
80 |
81 | Task-specific settings: ``PYLINT_RCFILE``
82 |
83 | .. _Pylint: http://www.logilab.org/project/pylint
84 |
85 | - ``django_jenkins.tasks.run_pep8``
86 |
87 | Runs pep8 tool over selected Django apps.
88 | Creates Pylint compatible report for Jenkins
89 |
90 | You should have pep8_ python package (>=1.3) installed to run this task.
91 |
92 | Task-specific settings: ``PEP8_RCFILE``
93 |
94 | .. _pep8: http://pypi.python.org/pypi/pep8
95 |
96 | - ``django_jenkins.tasks.run_pyflakes``
97 |
98 | Runs Pyflakes tool over selected Django apps.
99 | Creates Pylint compatible report for Jenkins.
100 |
101 | You should have Pyflakes_ python package installed to run this task.
102 |
103 | .. _Pyflakes: http://pypi.python.org/pypi/pyflakes
104 |
105 | - ``django_jenkins.tasks.run_flake8``
106 |
107 | Runs flake8 tool over selected Django apps.
108 | Creates pep8 compatible report for Jenkins.
109 |
110 | You should have flake8_ python package installed to run this task.
111 |
112 | .. _flake8: http://pypi.python.org/pypi/flake8
113 |
114 |
115 | Changelog
116 | ---------
117 |
118 | GIT Version
119 | ~~~~~~~~~~~~~~~~~~
120 |
121 | * csslint/jshint/sccsLint/sloccount task removed (could be used as standalone tools)
122 |
123 |
124 | Contribution guide
125 | ~~~~~~~~~~~~~~~~~~
126 |
127 | * Set up local jenkins
128 | * Set up django-jenkins::
129 |
130 | npm install jshint
131 | npm install csslint
132 | PATH=$PATH:$WORKSPACE/node_modules/.bin
133 | tox
134 |
135 | * Ensure that everything works
136 | * Modify the *the only one thing*
137 | * Ensure that everything works again
138 | * Fix pep8/pyflakes errors and minimize pylint's warnings
139 | * Pull request!
140 |
141 | Authors
142 | -------
143 | Created and maintained by Mikhail Podgurskiy
144 |
145 | Contributors: https://github.com/kmmbvnr/django-jenkins/graphs/contributors
146 |
147 | Special thanks, for all github forks authors for project extensions ideas and problem identifications.
148 |
--------------------------------------------------------------------------------
/django_jenkins/__init__.py:
--------------------------------------------------------------------------------
1 | default_app_config = 'django_jenkins.apps.JenkinsConfig'
2 |
--------------------------------------------------------------------------------
/django_jenkins/apps.py:
--------------------------------------------------------------------------------
1 | import sys
2 | from django.apps import AppConfig
3 | from django_jenkins.tasks.with_coverage import CoverageReporter
4 |
5 |
6 | class JenkinsConfig(AppConfig):
7 | """
8 | Enable coverage measurement as soon as possible
9 | """
10 | name = 'django_jenkins'
11 |
12 | def __init__(self, app_name, app_module):
13 | super(JenkinsConfig, self).__init__(app_name, app_module)
14 |
15 | self.coverage = None
16 |
17 | if 'jenkins' in sys.argv and '--enable-coverage' in sys.argv:
18 | """
19 | Starting coverage as soon as possible
20 | """
21 | self.coverage = CoverageReporter()
22 |
--------------------------------------------------------------------------------
/django_jenkins/management/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmmbvnr/django-jenkins/f673c8ecded24eb56aeaef8d1c0caeb82208b595/django_jenkins/management/__init__.py
--------------------------------------------------------------------------------
/django_jenkins/management/commands/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmmbvnr/django-jenkins/f673c8ecded24eb56aeaef8d1c0caeb82208b595/django_jenkins/management/commands/__init__.py
--------------------------------------------------------------------------------
/django_jenkins/management/commands/jenkins.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import warnings
4 | from importlib import import_module
5 |
6 | from django.apps import apps
7 | from django.conf import settings
8 | from django.core.management.commands.test import Command as TestCommand
9 |
10 | from django_jenkins.runner import CITestSuiteRunner
11 |
12 |
13 | def get_runner(settings, test_runner_class=None):
14 | if test_runner_class is None:
15 | test_runner_class = getattr(
16 | settings, 'JENKINS_TEST_RUNNER', 'django_jenkins.runner.CITestSuiteRunner')
17 |
18 | test_module_name, test_cls = test_runner_class.rsplit('.', 1)
19 | test_module = __import__(test_module_name, {}, {}, test_cls)
20 | test_runner = getattr(test_module, test_cls)
21 |
22 | if not issubclass(test_runner, CITestSuiteRunner):
23 | raise ValueError('Your custom TestRunner should extend '
24 | 'the CITestSuiteRunner class.')
25 | return test_runner
26 |
27 |
28 | class Command(TestCommand):
29 | def __init__(self):
30 | self.test_runner = None
31 | self.tasks_cls = [import_module(module_name).Reporter
32 | for module_name in self.get_task_list()]
33 | self.tasks = [task_cls() for task_cls in self.tasks_cls]
34 | super(Command, self).__init__()
35 |
36 | def run_from_argv(self, argv):
37 | """
38 | Pre-parse the command line to extract the value of the --testrunner
39 | option. This allows a test runner to define additional command line
40 | arguments.
41 | """
42 | option = '--testrunner='
43 | for arg in argv[2:]:
44 | if arg.startswith(option):
45 | self.test_runner = arg[len(option):]
46 | break
47 | super(Command, self).run_from_argv(argv)
48 |
49 | def get_task_list(self):
50 | return getattr(settings, 'JENKINS_TASKS', ())
51 |
52 | def add_arguments(self, parser):
53 | super(Command, self).add_arguments(parser)
54 | parser.add_argument('--output-dir', dest='output_dir', default="reports",
55 | help='Report files directory'),
56 | parser.add_argument("--enable-coverage",
57 | action="store_true", default=False,
58 | help="Measure code coverage"),
59 | parser.add_argument('--debug', action='store_true',
60 | dest='debug', default=False,
61 | help='Do not intercept stdout and stderr, friendly for console debuggers'),
62 | parser.add_argument("--coverage-rcfile",
63 | dest="coverage_rcfile",
64 | default="",
65 | help="Specify configuration file."),
66 | parser.add_argument("--coverage-format",
67 | dest="coverage_format",
68 | default="xml",
69 | help="Specify coverage output formats html,xml,bin"),
70 | parser.add_argument("--coverage-exclude", action="append",
71 | default=[], dest="coverage_excludes",
72 | help="Module name to exclude"),
73 | parser.add_argument("--project-apps-tests", action="store_true",
74 | default=False, dest="project_apps_tests",
75 | help="Take tests only from project apps")
76 |
77 | parser._optionals.conflict_handler = 'resolve'
78 | for task in self.tasks:
79 | if hasattr(task, 'add_arguments'):
80 | task.add_arguments(parser)
81 |
82 | test_runner_class = get_runner(settings, self.test_runner)
83 |
84 | if hasattr(test_runner_class, 'add_arguments'):
85 | test_runner_class.add_arguments(parser)
86 |
87 | def handle(self, *test_labels, **options):
88 | TestRunner = get_runner(settings, options['testrunner'])
89 | options['verbosity'] = int(options.get('verbosity'))
90 |
91 | if options.get('liveserver') is not None:
92 | os.environ['DJANGO_LIVE_TEST_SERVER_ADDRESS'] = options['liveserver']
93 | del options['liveserver']
94 |
95 | output_dir = options['output_dir']
96 | if not os.path.exists(output_dir):
97 | os.makedirs(output_dir)
98 |
99 | test_runner = TestRunner(**options)
100 |
101 | if not test_labels and options['project_apps_tests']:
102 | test_labels = getattr(settings, 'PROJECT_APPS', [])
103 |
104 | failures = test_runner.run_tests(test_labels)
105 |
106 | if failures:
107 | sys.exit(bool(failures))
108 | else:
109 | tested_locations = self.get_tested_locations(test_labels)
110 |
111 | coverage = apps.get_app_config('django_jenkins').coverage
112 | if coverage:
113 | if options['verbosity'] >= 1:
114 | print('Storing coverage info...')
115 |
116 | coverage.save(tested_locations, options)
117 |
118 | # run reporters
119 | for task in self.tasks:
120 | if options['verbosity'] >= 1:
121 | print('Executing {0}...'.format(task.__module__))
122 | task.run(tested_locations, **options)
123 |
124 | if options['verbosity'] >= 1:
125 | print('Done')
126 |
127 | def get_tested_locations(self, test_labels):
128 | locations = []
129 |
130 | coverage = apps.get_app_config('django_jenkins').coverage
131 | if test_labels:
132 | pass
133 | elif hasattr(settings, 'PROJECT_APPS'):
134 | test_labels = settings.PROJECT_APPS
135 | elif coverage and coverage.coverage.source:
136 | warnings.warn("No PROJECT_APPS settings, using 'source' config from rcfile")
137 | locations = coverage.coverage.source
138 | else:
139 | warnings.warn('No PROJECT_APPS settings, coverage gathered over all apps')
140 | test_labels = settings.INSTALLED_APPS
141 |
142 | for test_label in test_labels:
143 | app_config = apps.get_containing_app_config(test_label)
144 | if app_config is not None:
145 | locations.append(app_config.path)
146 | else:
147 | warnings.warn('No app found for test: {0}'.format(test_label))
148 |
149 | return locations
150 |
--------------------------------------------------------------------------------
/django_jenkins/models.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmmbvnr/django-jenkins/f673c8ecded24eb56aeaef8d1c0caeb82208b595/django_jenkins/models.py
--------------------------------------------------------------------------------
/django_jenkins/runner.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | import time
4 | from unittest import TextTestResult
5 |
6 | from xml.etree import ElementTree as ET
7 |
8 | from django.test.runner import DiscoverRunner
9 | from django.utils.encoding import smart_text
10 |
11 |
12 | class EXMLTestResult(TextTestResult):
13 | def __init__(self, *args, **kwargs):
14 | self.case_start_time = time.time()
15 | self.run_start_time = None
16 | self.tree = None
17 | super(EXMLTestResult, self).__init__(*args, **kwargs)
18 |
19 | def startTest(self, test):
20 | self.case_start_time = time.time()
21 | super(EXMLTestResult, self).startTest(test)
22 |
23 | def startTestRun(self):
24 | self.tree = ET.Element('testsuite')
25 | self.run_start_time = time.time()
26 | super(EXMLTestResult, self).startTestRun()
27 |
28 | def addSuccess(self, test):
29 | self.testcase = self._make_testcase_element(test)
30 | super(EXMLTestResult, self).addSuccess(test)
31 |
32 | def addFailure(self, test, err):
33 | self.testcase = self._make_testcase_element(test)
34 | test_result = ET.SubElement(self.testcase, 'failure')
35 | self._add_tb_to_test(test, test_result, err)
36 | super(EXMLTestResult, self).addFailure(test, err)
37 |
38 | def addError(self, test, err):
39 | self.testcase = self._make_testcase_element(test)
40 | test_result = ET.SubElement(self.testcase, 'error')
41 | self._add_tb_to_test(test, test_result, err)
42 | super(EXMLTestResult, self).addError(test, err)
43 |
44 | def addUnexpectedSuccess(self, test):
45 | self.testcase = self._make_testcase_element(test)
46 | test_result = ET.SubElement(self.testcase, 'skipped')
47 | test_result.set('message', 'Test Skipped: Unexpected Success')
48 | super(EXMLTestResult, self).addUnexpectedSuccess(test)
49 |
50 | def addSkip(self, test, reason):
51 | self.testcase = self._make_testcase_element(test)
52 | test_result = ET.SubElement(self.testcase, 'skipped')
53 | test_result.set('message', 'Test Skipped: %s' % reason)
54 | super(EXMLTestResult, self).addSkip(test, reason)
55 |
56 | def addExpectedFailure(self, test, err):
57 | self.testcase = self._make_testcase_element(test)
58 | test_result = ET.SubElement(self.testcase, 'skipped')
59 | self._add_tb_to_test(test, test_result, err)
60 | super(EXMLTestResult, self).addExpectedFailure(test, err)
61 |
62 | def stopTest(self, test):
63 | if self.buffer:
64 | output = sys.stdout.getvalue() if hasattr(sys.stdout, 'getvalue') else ''
65 | if output:
66 | sysout = ET.SubElement(self.testcase, 'system-out')
67 | sysout.text = smart_text(output, errors='ignore')
68 |
69 | error = sys.stderr.getvalue() if hasattr(sys.stderr, 'getvalue') else ''
70 | if error:
71 | syserr = ET.SubElement(self.testcase, 'system-err')
72 | syserr.text = smart_text(error, errors='ignore')
73 |
74 | super(EXMLTestResult, self).stopTest(test)
75 |
76 | def stopTestRun(self):
77 | run_time_taken = time.time() - self.run_start_time
78 | self.tree.set('name', 'Django Project Tests')
79 | self.tree.set('errors', str(len(self.errors)))
80 | self.tree.set('failures', str(len(self.failures)))
81 | self.tree.set('skips', str(len(self.skipped)))
82 | self.tree.set('tests', str(self.testsRun))
83 | self.tree.set('time', "%.3f" % run_time_taken)
84 | super(EXMLTestResult, self).stopTestRun()
85 |
86 | def _make_testcase_element(self, test):
87 | time_taken = time.time() - self.case_start_time
88 | classname = ('%s.%s' % (test.__module__, test.__class__.__name__)).split('.')
89 | testcase = ET.SubElement(self.tree, 'testcase')
90 | testcase.set('time', "%.6f" % time_taken)
91 | testcase.set('classname', '.'.join(classname))
92 | testcase.set('name', getattr(test, '_testMethodName',
93 | getattr(test, 'description', 'UNKNOWN')))
94 | return testcase
95 |
96 | def _restoreStdout(self):
97 | '''Disables buffering once the stdout/stderr are reset.'''
98 | super(EXMLTestResult, self)._restoreStdout()
99 | self.buffer = False
100 |
101 | def _add_tb_to_test(self, test, test_result, err):
102 | '''Add a traceback to the test result element'''
103 | exc_class, exc_value, tb = err
104 | tb_str = self._exc_info_to_string(err, test)
105 | test_result.set('type', '%s.%s' % (exc_class.__module__, exc_class.__name__))
106 | test_result.set('message', smart_text(exc_value))
107 | test_result.text = smart_text(tb_str)
108 |
109 | def dump_xml(self, output_dir):
110 | """
111 | Dumps test result to xml
112 | """
113 | if not os.path.exists(output_dir):
114 | os.makedirs(output_dir)
115 |
116 | output = ET.ElementTree(self.tree)
117 | output.write(os.path.join(output_dir, 'junit.xml'), encoding="utf-8")
118 |
119 |
120 | class CITestSuiteRunner(DiscoverRunner):
121 | def __init__(self, output_dir='reports', debug=False, **kwargs):
122 | self.output_dir = output_dir
123 | self.debug = debug
124 | super(CITestSuiteRunner, self).__init__(**kwargs)
125 |
126 | def run_suite(self, suite, **kwargs):
127 | result = self.test_runner(
128 | verbosity=self.verbosity,
129 | failfast=self.failfast,
130 | resultclass=EXMLTestResult,
131 | buffer=not self.debug
132 | ).run(suite)
133 |
134 | result.dump_xml(self.output_dir)
135 |
136 | return result
137 |
--------------------------------------------------------------------------------
/django_jenkins/tasks/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | import itertools
3 |
4 | from django.conf import settings
5 | from django.contrib.staticfiles import finders
6 |
7 |
8 | def static_files_iterator(tested_locations, extension, ignore_patterns=None, additional_settings_list=None):
9 | if ignore_patterns is None:
10 | ignore_patterns = []
11 |
12 | source = (os.path.join(storage.location, path)
13 | for finder in finders.get_finders()
14 | for path, storage in finder.list(ignore_patterns))
15 |
16 | if additional_settings_list and hasattr(settings, additional_settings_list):
17 | source = itertools.chain(source, getattr(settings, additional_settings_list))
18 |
19 | return (path for path in source
20 | if path.endswith(extension)
21 | if any(path.startswith(location) for location in tested_locations))
22 |
23 |
24 | def set_option(conf_dict, opt_name, opt_value, conf_file, default=None, split=None):
25 | if conf_file is None:
26 | if opt_value is None:
27 | opt_value = default
28 |
29 | if opt_value:
30 | if split:
31 | opt_value = opt_value.split(split)
32 |
33 | conf_dict[opt_name] = opt_value
34 |
--------------------------------------------------------------------------------
/django_jenkins/tasks/pylint.rc:
--------------------------------------------------------------------------------
1 | [MASTER]
2 | persistent=yes
3 | ignore=south_migrations
4 | cache-size=500
5 |
6 | [MESSAGES CONTROL]
7 | # C0111 Missing docstring
8 | # I0011 Warning locally suppressed using disable-msg
9 | # I0012 Warning locally suppressed using disable-msg
10 | # W0704 Except doesn't do anything Used when an except clause does nothing but "pass" and there is no "else" clause
11 | # W0142 Used * or * magic* Used when a function or method is called using *args or **kwargs to dispatch arguments.
12 | # W0212 Access to a protected member %s of a client class
13 | # W0232 Class has no __init__ method Used when a class has no __init__ method, neither its parent classes.
14 | # W0613 Unused argument %r Used when a function or method argument is not used.
15 | # W0702 No exception's type specified Used when an except clause doesn't specify exceptions type to catch.
16 | # R0201 Method could be a function
17 | # C1001 Used when a class is defined that does not inherit from anotherclass and does not inherit explicitly from "object".
18 | # C0103 Invalid module name
19 | # R0901 Used when class has too many parent classes, try to reduce this to get a simpler (and so easier to use) class.
20 | disable=C0111,I0011,I0012,W0704,W0142,W0212,W0232,W0613,W0702,R0201,C1001,C0103,R0901
21 |
22 | [REPORTS]
23 | msg-template={path}:{line}: [{msg_id}({symbol}), {obj}] {msg}
24 |
25 |
26 | [BASIC]
27 | no-docstring-rgx=__.*__|_.*
28 | class-rgx=[A-Z_][a-zA-Z0-9_]+$
29 | function-rgx=[a-zA_][a-zA-Z0-9_]{2,70}$
30 | method-rgx=[a-z_][a-zA-Z0-9_]{2,70}$
31 | const-rgx=(([A-Z_][A-Z0-9_]*)|([a-z_][a-z0-9_]*)|(__.*__)|register|urlpatterns)$
32 | good-names=_,i,j,k,e,qs,pk,setUp,tearDown
33 |
34 | [TYPECHECK]
35 |
36 | # Tells whether missing members accessed in mixin class should be ignored. A
37 | # mixin class is detected if its name ends with "mixin" (case insensitive).
38 | ignore-mixin-members=yes
39 |
40 | # List of classes names for which member attributes should not be checked
41 | # (useful for classes with attributes dynamically set).
42 | ignored-classes=SQLObject,WSGIRequest
43 |
44 | # List of members which are set dynamically and missed by pylint inference
45 | # system, and so shouldn't trigger E0201 when accessed.
46 | generated-members=objects,DoesNotExist,id,pk,_meta,base_fields,context
47 |
48 | # List of method names used to declare (i.e. assign) instance attributes
49 | defining-attr-methods=__init__,__new__,setUp
50 |
51 |
52 | [VARIABLES]
53 | init-import=no
54 | dummy-variables-rgx=_|dummy
55 |
56 | [SIMILARITIES]
57 | min-similarity-lines=6
58 | ignore-comments=yes
59 | ignore-docstrings=yes
60 |
61 |
62 | [MISCELLANEOUS]
63 | notes=FIXME,XXX,TODO
64 |
65 |
66 | [FORMAT]
67 | max-line-length=160
68 | max-module-lines=500
69 | indent-string=' '
70 |
71 |
72 | [DESIGN]
73 | max-args=10
74 | max-locals=15
75 | max-returns=6
76 | max-branches=12
77 | max-statements=50
78 | max-parents=7
79 | max-attributes=7
80 | min-public-methods=0
81 | max-public-methods=50
82 |
--------------------------------------------------------------------------------
/django_jenkins/tasks/run_flake8.py:
--------------------------------------------------------------------------------
1 | import os
2 | import pep8
3 | import sys
4 |
5 | try:
6 | from StringIO import StringIO
7 | except ImportError:
8 | from io import StringIO
9 |
10 |
11 | from flake8.api.legacy import get_style_guide # Quck hack again, 3d time flake8 would be removed, if no volounters found
12 | from django.conf import settings
13 |
14 | from . import set_option
15 |
16 |
17 | class Reporter(object):
18 | """
19 | Runs flake8 on python files.
20 | """
21 | def add_arguments(self, parser):
22 | parser.add_argument('--max-complexity',
23 | dest='flake8-max-complexity',
24 | type=int,
25 | help='McCabe complexity treshold')
26 | parser.add_argument("--pep8-exclude",
27 | dest="pep8-exclude",
28 | help="exclude files or directories which match these "
29 | "comma separated patterns (default: %s)" %
30 | (pep8.DEFAULT_EXCLUDE + ",south_migrations"))
31 | parser.add_argument("--pep8-select", dest="pep8-select",
32 | help="select errors and warnings (e.g. E,W6)")
33 | parser.add_argument("--pep8-ignore", dest="pep8-ignore",
34 | help="skip errors and warnings (e.g. E4,W)")
35 | parser.add_argument("--pep8-max-line-length",
36 | dest="pep8-max-line-length", type=int,
37 | help="set maximum allowed line length (default: %d)" %
38 | pep8.MAX_LINE_LENGTH)
39 | parser.add_argument("--pep8-rcfile", dest="pep8-rcfile",
40 | help="PEP8 configuration file")
41 |
42 | def run(self, apps_locations, **options):
43 | output = open(os.path.join(options['output_dir'], 'flake8.report'), 'w')
44 |
45 | pep8_options = {}
46 |
47 | config_file = self.get_config_path(options)
48 | if config_file is not None:
49 | pep8_options['config_file'] = config_file
50 |
51 | set_option(pep8_options, 'exclude', options['pep8-exclude'], config_file,
52 | default=pep8.DEFAULT_EXCLUDE + ",south_migrations", split=',')
53 |
54 | set_option(pep8_options, 'select', options['pep8-select'], config_file, split=',')
55 |
56 | set_option(pep8_options, 'ignore', options['pep8-ignore'], config_file, split=',')
57 |
58 | set_option(pep8_options, 'max_line_length', options['pep8-max-line-length'], config_file,
59 | default=pep8.MAX_LINE_LENGTH)
60 |
61 | set_option(pep8_options, 'max_complexity', options['flake8-max-complexity'], config_file,
62 | default=-1)
63 |
64 | old_stdout, flake8_output = sys.stdout, StringIO()
65 | sys.stdout = flake8_output
66 |
67 | pep8style = get_style_guide(
68 | parse_argv=False,
69 | jobs='1',
70 | **pep8_options)
71 |
72 | try:
73 | for location in apps_locations:
74 | pep8style.input_file(os.path.relpath(location))
75 | finally:
76 | sys.stdout = old_stdout
77 |
78 | flake8_output.seek(0)
79 | output.write(flake8_output.read())
80 | output.close()
81 |
82 | def get_config_path(self, options):
83 | if options['pep8-rcfile']:
84 | return options['pep8-rcfile']
85 |
86 | rcfile = getattr(settings, 'PEP8_RCFILE', None)
87 | if rcfile:
88 | return rcfile
89 |
90 | if os.path.exists('tox.ini'):
91 | return 'tox.ini'
92 |
93 | if os.path.exists('setup.cfg'):
94 | return 'setup.cfg'
95 |
--------------------------------------------------------------------------------
/django_jenkins/tasks/run_pep8.py:
--------------------------------------------------------------------------------
1 | import os.path
2 | import pep8
3 |
4 | from django.conf import settings
5 |
6 | from . import set_option
7 |
8 |
9 | class Reporter(object):
10 | def add_arguments(self, parser):
11 | parser.add_argument("--pep8-exclude",
12 | dest="pep8-exclude",
13 | help="exclude files or directories which match these "
14 | "comma separated patterns (default: %s)" %
15 | (pep8.DEFAULT_EXCLUDE + ",south_migrations"))
16 | parser.add_argument("--pep8-select", dest="pep8-select",
17 | help="select errors and warnings (e.g. E,W6)")
18 | parser.add_argument("--pep8-ignore", dest="pep8-ignore",
19 | help="skip errors and warnings (e.g. E4,W)"),
20 | parser.add_argument("--pep8-max-line-length",
21 | dest="pep8-max-line-length", type=int,
22 | help="set maximum allowed line length (default: %d)" % pep8.MAX_LINE_LENGTH)
23 | parser.add_argument("--pep8-rcfile", dest="pep8-rcfile",
24 | help="PEP8 configuration file")
25 |
26 | def run(self, apps_locations, **options):
27 | output = open(os.path.join(options['output_dir'], 'pep8.report'), 'w')
28 |
29 | class JenkinsReport(pep8.BaseReport):
30 | def error(instance, line_number, offset, text, check):
31 | code = super(JenkinsReport, instance).error(line_number, offset, text, check)
32 | if code:
33 | sourceline = instance.line_offset + line_number
34 | output.write('%s:%s:%s: %s\n' % (instance.filename, sourceline, offset + 1, text))
35 |
36 | pep8_options = {}
37 | config_file = self.get_config_path(options)
38 | if config_file is not None:
39 | pep8_options['config_file'] = config_file
40 |
41 | set_option(pep8_options, 'exclude', options['pep8-exclude'], config_file,
42 | default=pep8.DEFAULT_EXCLUDE + ",south_migrations", split=',')
43 |
44 | set_option(pep8_options, 'select', options['pep8-select'], config_file, split=',')
45 |
46 | set_option(pep8_options, 'ignore', options['pep8-ignore'], config_file, split=',')
47 |
48 | set_option(pep8_options, 'max_line_length', options['pep8-max-line-length'], config_file,
49 | default=pep8.MAX_LINE_LENGTH)
50 |
51 | pep8style = pep8.StyleGuide(
52 | parse_argv=False,
53 | reporter=JenkinsReport,
54 | **pep8_options)
55 |
56 | pep8style.options.report.start()
57 | for location in apps_locations:
58 | pep8style.input_dir(os.path.relpath(location))
59 | pep8style.options.report.stop()
60 |
61 | output.close()
62 |
63 | def get_config_path(self, options):
64 | if options['pep8-rcfile']:
65 | return options['pep8-rcfile']
66 |
67 | rcfile = getattr(settings, 'PEP8_RCFILE', None)
68 | if rcfile:
69 | return rcfile
70 |
71 | if os.path.exists('tox.ini'):
72 | return 'tox.ini'
73 |
74 | if os.path.exists('setup.cfg'):
75 | return 'setup.cfg'
76 |
--------------------------------------------------------------------------------
/django_jenkins/tasks/run_pyflakes.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import os
3 | import re
4 | import sys
5 | from pyflakes.scripts import pyflakes
6 |
7 | try:
8 | from StringIO import StringIO
9 | except ImportError:
10 | from io import StringIO
11 |
12 |
13 | class Reporter(object):
14 | def add_arguments(self, parser):
15 | parser.add_argument("--pyflakes-exclude-dir",
16 | action="append",
17 | default=['south_migrations'],
18 | dest="pyflakes_exclude_dirs",
19 | help="Path name to exclude")
20 |
21 | def run(self, apps_locations, **options):
22 | output = open(os.path.join(options['output_dir'], 'pyflakes.report'), 'w')
23 |
24 | # run pyflakes tool with captured output
25 | old_stdout, pyflakes_output = sys.stdout, StringIO()
26 | sys.stdout = pyflakes_output
27 | try:
28 | for location in apps_locations:
29 | if os.path.isdir(location):
30 | for dirpath, dirnames, filenames in os.walk(os.path.relpath(location)):
31 | if dirpath.endswith(tuple(
32 | ''.join([os.sep, exclude_dir]) for exclude_dir in options['pyflakes_exclude_dirs'])):
33 | continue
34 |
35 | for filename in filenames:
36 | if filename.endswith('.py'):
37 | pyflakes.checkPath(os.path.join(dirpath, filename))
38 | else:
39 | pyflakes.checkPath(os.path.relpath(location))
40 | finally:
41 | sys.stdout = old_stdout
42 |
43 | # save report
44 | pyflakes_output.seek(0)
45 |
46 | while True:
47 | line = pyflakes_output.readline()
48 | if not line:
49 | break
50 | message = re.sub(r': ', r': [E] PYFLAKES:', line)
51 | output.write(message)
52 |
53 | output.close()
54 |
--------------------------------------------------------------------------------
/django_jenkins/tasks/run_pylint.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | from django.conf import settings
4 |
5 | from pylint import lint
6 | from pylint.reporters.text import TextReporter
7 |
8 |
9 | class ParseableTextReporter(TextReporter):
10 | """
11 | Outputs messages in a form recognized by jenkins
12 |
13 | ::
14 | """
15 | name = 'parseable'
16 | line_format = '{path}:{line}: [{msg_id}({symbol}), {obj}] {msg}'
17 |
18 |
19 | class Reporter(object):
20 | def add_arguments(self, parser):
21 | parser.add_argument("--pylint-rcfile",
22 | dest="pylint_rcfile",
23 | help="pylint configuration file")
24 | parser.add_argument("--pylint-errors-only",
25 | dest="pylint_errors_only",
26 | action="store_true", default=False,
27 | help="pylint output errors only mode")
28 | parser.add_argument("--pylint-load-plugins",
29 | dest="pylint_load_plugins",
30 | help="list of pylint plugins to load")
31 |
32 | def run(self, apps_locations, **options):
33 | output = open(os.path.join(options['output_dir'], 'pylint.report'), 'w')
34 |
35 | args = []
36 | args.append("--rcfile=%s" % self.get_config_path(options))
37 | if self.get_plugins(options):
38 | args.append('--load-plugins=%s' % self.get_plugins(options))
39 |
40 | if options['pylint_errors_only']:
41 | args += ['--errors-only']
42 | args += apps_locations
43 |
44 | lint.Run(args, reporter=ParseableTextReporter(output=output), exit=False)
45 |
46 | output.close()
47 |
48 | def get_plugins(self, options):
49 | if options.get('pylint_load_plugins', None):
50 | return options['pylint_load_plugins']
51 |
52 | plugins = getattr(settings, 'PYLINT_LOAD_PLUGIN', None)
53 | if plugins:
54 | return ','.join(plugins)
55 |
56 | return None
57 |
58 | def get_config_path(self, options):
59 | if options['pylint_rcfile']:
60 | return options['pylint_rcfile']
61 |
62 | rcfile = getattr(settings, 'PYLINT_RCFILE', 'pylint.rc')
63 | if os.path.exists(rcfile):
64 | return rcfile
65 |
66 | # use built-in
67 | root_dir = os.path.normpath(os.path.dirname(__file__))
68 | return os.path.join(root_dir, 'pylint.rc')
69 |
--------------------------------------------------------------------------------
/django_jenkins/tasks/with_coverage.py:
--------------------------------------------------------------------------------
1 | import os
2 | import sys
3 | from importlib import import_module
4 |
5 | from django.conf import settings
6 |
7 |
8 | class CoverageReporter(object):
9 | def __init__(self):
10 | try:
11 | import coverage
12 | except ImportError:
13 | raise ImportError('coverage is not installed')
14 |
15 | if coverage.__version__ < '4':
16 | raise ImportError('coverage>=4 required')
17 |
18 | coverage_config_file = None
19 | for argv in sys.argv:
20 | if argv.startswith('--coverage-rcfile='):
21 | _, coverage_config_file = argv.split('=')
22 |
23 | self.coverage = coverage.coverage(
24 | branch=True,
25 | config_file=coverage_config_file or self.default_coverage_config())
26 | self.coverage.start()
27 |
28 | def save(self, apps_locations, options):
29 | self.coverage.stop()
30 | self.coverage.get_data()
31 | morfs = self.get_morfs(self.coverage, apps_locations, options)
32 |
33 | if 'xml' in options['coverage_format']:
34 | self.coverage.xml_report(morfs=morfs, outfile=os.path.join(options['output_dir'], 'coverage.xml'))
35 | if 'bin' in options['coverage_format']:
36 | self.coverage.save()
37 | if 'html' in options['coverage_format']:
38 | # Dump coverage html
39 | self.coverage.html_report(morfs=morfs, directory=os.path.join(options['output_dir'], 'coverage'))
40 |
41 | def get_morfs(self, coverage, tested_locations, options):
42 | excluded = []
43 |
44 | # Exclude by module
45 | modnames = options.get('coverage_excludes') or getattr(settings, 'COVERAGE_EXCLUDES', [])
46 | for modname in modnames:
47 | try:
48 | excluded.append(os.path.dirname(import_module(modname).__file__))
49 | except ImportError:
50 | pass
51 |
52 | # Exclude by directory
53 | excluded.extend(getattr(settings, 'COVERAGE_EXCLUDES_FOLDERS', []))
54 |
55 | return [filename for filename in coverage.data.measured_files()
56 | if not (os.sep + 'migrations' + os.sep) in filename
57 | if not (os.sep + 'south_migrations' + os.sep) in filename
58 | if any(filename.startswith(location) for location in tested_locations)
59 | if not any(filename.startswith(location) for location in excluded)]
60 |
61 | def default_coverage_config(self):
62 | rcfile = getattr(settings, 'COVERAGE_RCFILE', 'coverage.rc')
63 | if os.path.exists(rcfile):
64 | return rcfile
65 | return None
66 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [wheel]
2 | universal = 1
3 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | import codecs
5 | from os import path
6 | from setuptools import setup
7 |
8 |
9 | read = lambda filepath: codecs.open(filepath, 'r', 'utf-8').read()
10 |
11 |
12 | setup(
13 | name='django-jenkins',
14 | version='1.11.0',
15 | author='Mikhail Podgurskiy',
16 | author_email='kmmbvnr@gmail.com',
17 | description='Plug and play continuous integration with django and jenkins',
18 | long_description=read(path.abspath(path.join(path.dirname(__file__), 'README.rst'))),
19 | license='LGPL',
20 | platforms=['Any'],
21 | keywords=['pyunit', 'unittest', 'testrunner', 'hudson', 'jenkins',
22 | 'django', 'pylint', 'pep8', 'pyflakes', 'csslint', 'scsslint',
23 | 'jshint', 'coverage'],
24 | url='http://github.com/kmmbvnr/django-jenkins',
25 | classifiers=[
26 | 'Development Status :: 5 - Production/Stable',
27 | 'Intended Audience :: Developers',
28 | 'License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)',
29 | 'Natural Language :: English',
30 | 'Operating System :: OS Independent',
31 | "Framework :: Django",
32 | "Framework :: Django :: 1.8",
33 | "Framework :: Django :: 1.9",
34 | 'Programming Language :: Python',
35 | 'Programming Language :: Python :: 2.7',
36 | 'Programming Language :: Python :: 3',
37 | 'Programming Language :: Python :: 3.4',
38 | 'Programming Language :: Python :: 3.5',
39 | 'Topic :: Software Development :: Libraries :: Python Modules',
40 | 'Topic :: Software Development :: Testing'
41 | ],
42 | install_requires=[
43 | 'Django>=1.8',
44 | ],
45 | packages=['django_jenkins', 'django_jenkins.management',
46 | 'django_jenkins.tasks', 'django_jenkins.management.commands'],
47 | package_data={'django_jenkins': ['tasks/pylint.rc']},
48 | zip_safe=False,
49 | include_package_data=True
50 | )
51 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmmbvnr/django-jenkins/f673c8ecded24eb56aeaef8d1c0caeb82208b595/tests/__init__.py
--------------------------------------------------------------------------------
/tests/manage.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import os, sys
3 | from django.core.management import execute_from_command_line
4 |
5 | PROJECT_ROOT = os.path.dirname(os.path.abspath(os.path.dirname(__file__)))
6 | sys.path.insert(0, PROJECT_ROOT)
7 |
8 | if __name__ == "__main__":
9 | if len(sys.argv) == 1:
10 | sys.argv += ['test']
11 |
12 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.settings")
13 | execute_from_command_line(sys.argv)
14 |
--------------------------------------------------------------------------------
/tests/runner.py:
--------------------------------------------------------------------------------
1 | from django_jenkins.runner import CITestSuiteRunner
2 |
3 |
4 | class CustomTestRunner(CITestSuiteRunner):
5 | @classmethod
6 | def add_arguments(self, parser):
7 | parser.add_argument('--ok', default=False, action='store_true', help='Custom test runner option')
8 |
--------------------------------------------------------------------------------
/tests/settings.py:
--------------------------------------------------------------------------------
1 | import os
2 |
3 | PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__))
4 |
5 | DEBUG = True
6 | TEMPLATE_DEBUG = DEBUG
7 | ROOT_URLCONF = 'test_app.urls'
8 | SECRET_KEY = 'nokey'
9 | MIDDLEWARE_CLASSES = ()
10 |
11 | TEMPLATE_LOADERS = (
12 | 'django.template.loaders.filesystem.Loader',
13 | 'django.template.loaders.app_directories.Loader',
14 | )
15 |
16 | TEMPLATES = [
17 | {
18 | 'BACKEND': 'django.template.backends.django.DjangoTemplates',
19 | 'DIRS': [
20 | ],
21 | 'APP_DIRS': True,
22 | 'OPTIONS': {
23 | 'context_processors': [
24 | 'django.contrib.auth.context_processors.auth',
25 | 'django.template.context_processors.debug',
26 | 'django.template.context_processors.i18n',
27 | 'django.template.context_processors.media',
28 | 'django.template.context_processors.static',
29 | 'django.template.context_processors.tz',
30 | 'django.contrib.messages.context_processors.messages',
31 | ],
32 | },
33 | },
34 | ]
35 |
36 | PROJECT_APPS = (
37 | 'django.contrib.sessions', # just to ensure that dotted apps test works
38 | 'django_jenkins',
39 | 'tests.test_app',
40 | 'tests.test_app_dirs',
41 | )
42 |
43 | INSTALLED_APPS = (
44 | 'django.contrib.auth',
45 | 'django.contrib.contenttypes',
46 | ) + PROJECT_APPS
47 |
48 |
49 | DATABASE_ENGINE = 'sqlite3'
50 | DATABASES = {
51 | 'default': {
52 | 'ENGINE': 'django.db.backends.%s' % DATABASE_ENGINE,
53 | }
54 | }
55 |
56 | SOUTH_MIGRATION_MODULES = {
57 | 'test_app': 'test_app.south_migrations',
58 | }
59 |
60 | JENKINS_TASKS = (
61 | 'django_jenkins.tasks.run_pylint',
62 | 'django_jenkins.tasks.run_pep8',
63 | 'django_jenkins.tasks.run_pyflakes',
64 | 'django_jenkins.tasks.run_flake8',
65 | )
66 |
67 | COVERAGE_EXCLUDES = ['tests.test_app.not_for_coverage', ]
68 | COVERAGE_EXCLUDES_FOLDERS = [os.path.join(PROJECT_ROOT, 'test_app_dirs/not_for_coverage/'), ]
69 |
70 | # JSHINT_CHECKED_FILES = [os.path.join(PROJECT_ROOT, 'static/js/test.js')]
71 | # CSSLINT_CHECKED_FILES = [os.path.join(PROJECT_ROOT, 'static/css/test.css')]
72 |
73 | PYLINT_LOAD_PLUGIN = (
74 | 'pylint_django',
75 | )
76 |
77 | STATICFILES_DIRS = [
78 | os.path.join(PROJECT_ROOT, 'static/'),
79 | ]
80 |
81 | STATIC_URL = '/media/'
82 |
83 |
84 | LOGGING = {
85 | 'version': 1,
86 | 'disable_existing_loggers': False,
87 | 'handlers': {
88 | 'console': {
89 | 'level': 'DEBUG',
90 | 'class': 'logging.StreamHandler',
91 | },
92 | },
93 | 'loggers': {
94 | 'django.request': {
95 | 'handlers': ['console'],
96 | 'level': 'ERROR',
97 | 'propagate': True,
98 | },
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/tests/static/css/test.css:
--------------------------------------------------------------------------------
1 | h1 {
2 | /* empty ruleset, for testing csslint output*/
3 | }
--------------------------------------------------------------------------------
/tests/static/css/test.scss:
--------------------------------------------------------------------------------
1 | h1 {
2 |
3 | // empty rule and 4 spaces, for testing scsslint output
4 | .test {
5 |
6 | }
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/tests/static/js/test.js:
--------------------------------------------------------------------------------
1 | function toggle() {
2 | "use strict";
3 | var unused, x = true;
4 | if (x && y) {
5 | x = false;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/tests/test_app/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmmbvnr/django-jenkins/f673c8ecded24eb56aeaef8d1c0caeb82208b595/tests/test_app/__init__.py
--------------------------------------------------------------------------------
/tests/test_app/features/example.feature:
--------------------------------------------------------------------------------
1 | Feature: Addition
2 |
3 | Scenario: Simple addition between two numbers
4 | Given the number "1"
5 | When the number "1" is added to it
6 | Then the result should be "2"
7 |
8 | Scenario: Tricky addition between three numbers
9 | Given the number "1"
10 | And the number "7"
11 | When the number "6" is added to them
12 | Then the result should be "14"
--------------------------------------------------------------------------------
/tests/test_app/features/example_steps.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from lettuce import step, before
3 |
4 | @before.each_scenario
5 | def setup_scenario(scenario):
6 | scenario.numbers = []
7 |
8 | @step('(?:Given|And|When) the number "(.*)"(?: is added to (?:it|them))?')
9 | def given_the_number(step, number):
10 | step.scenario.numbers.append(int(number))
11 |
12 | @step('Then the result should be "(.*)"')
13 | def then_the_result_should_equal(step, result):
14 | actual = sum(step.scenario.numbers)
15 | assert int(result) == actual, "%s != %s" % (result, actual)
16 |
--------------------------------------------------------------------------------
/tests/test_app/migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from __future__ import unicode_literals
3 |
4 | from django.db import models, migrations
5 |
6 |
7 | class Migration(migrations.Migration):
8 |
9 | dependencies = [
10 | ]
11 |
12 | operations = [
13 | migrations.CreateModel(
14 | name='TestModel',
15 | fields=[
16 | ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
17 | ('test_text', models.CharField(max_length=250)),
18 | ],
19 | options={
20 | },
21 | bases=(models.Model,),
22 | ),
23 | ]
24 |
--------------------------------------------------------------------------------
/tests/test_app/migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmmbvnr/django-jenkins/f673c8ecded24eb56aeaef8d1c0caeb82208b595/tests/test_app/migrations/__init__.py
--------------------------------------------------------------------------------
/tests/test_app/models.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 | from test_app.not_for_coverage import one, two # NOQA
3 |
4 |
5 | class TestModel(models.Model):
6 | test_text = models.CharField(max_length=250)
7 |
--------------------------------------------------------------------------------
/tests/test_app/not_for_coverage/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmmbvnr/django-jenkins/f673c8ecded24eb56aeaef8d1c0caeb82208b595/tests/test_app/not_for_coverage/__init__.py
--------------------------------------------------------------------------------
/tests/test_app/not_for_coverage/one.py:
--------------------------------------------------------------------------------
1 | def test():
2 | print('Not for coverage')
--------------------------------------------------------------------------------
/tests/test_app/not_for_coverage/two.py:
--------------------------------------------------------------------------------
1 | def test():
2 | print('Not for coverage')
--------------------------------------------------------------------------------
/tests/test_app/south_migrations/0001_initial.py:
--------------------------------------------------------------------------------
1 | from south.v2 import SchemaMigration
2 |
3 | class Migration(SchemaMigration):
4 | def forwards(self, orm):
5 | a = 1 # pyflakes/pylint violation
6 | pass
7 |
8 | def backwards(self, orm):
9 | pass
10 |
--------------------------------------------------------------------------------
/tests/test_app/south_migrations/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmmbvnr/django-jenkins/f673c8ecded24eb56aeaef8d1c0caeb82208b595/tests/test_app/south_migrations/__init__.py
--------------------------------------------------------------------------------
/tests/test_app/static/css/test_errors.css:
--------------------------------------------------------------------------------
1 | h1 {
2 | /* unclosed tag produce error forcsslint
3 |
--------------------------------------------------------------------------------
/tests/test_app/static/js/test.js:
--------------------------------------------------------------------------------
1 | /*jshint unused:true */
2 |
3 | function toggle() {
4 | var unused, x = true;
5 | if (x) {
6 | x = false;
7 | }
8 | }
--------------------------------------------------------------------------------
/tests/test_app/templates/404.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmmbvnr/django-jenkins/f673c8ecded24eb56aeaef8d1c0caeb82208b595/tests/test_app/templates/404.html
--------------------------------------------------------------------------------
/tests/test_app/templates/500.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmmbvnr/django-jenkins/f673c8ecded24eb56aeaef8d1c0caeb82208b595/tests/test_app/templates/500.html
--------------------------------------------------------------------------------
/tests/test_app/templates/test_app/wm_test_click.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Windmill testing
5 |
6 |
7 |
8 |
9 |
Windmill testing
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/tests/test_app/tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | import io
3 | import sys
4 | from xml.etree import ElementTree as ET
5 |
6 | from django.core import mail
7 | from django.test import TestCase
8 | from unittest import skip
9 |
10 | from django.test import LiveServerTestCase
11 | from pyvirtualdisplay import Display
12 | from selenium import webdriver
13 |
14 | from django_jenkins.runner import EXMLTestResult
15 |
16 |
17 | class SaintyChecks(TestCase):
18 | # @classmethod
19 | # def setUpClass(cls):
20 | # raise Exception("Ups, should be disabled")
21 |
22 | def test_mailbox_stubs_not_broken(self):
23 | print("Testing mailbox django stubs")
24 | mail.send_mail('Test subject', 'Test message', 'nobody@kenkins.com',
25 | ['somewhere@nowhere.com'])
26 | self.assertTrue(1, len(mail.outbox))
27 |
28 | @skip("Check skiped test")
29 | def test_is_skipped(self):
30 | print("This test should be skipped")
31 |
32 | def test_junit_xml_with_utf8_stdout_and_stderr(self):
33 | sys.stdout.write('\xc4\x85')
34 | sys.stderr.write('\xc4\x85')
35 |
36 | def test_junit_xml_with_invalid_stdout_and_stderr_encoding(self):
37 | sys.stdout.write('\xc4')
38 | sys.stderr.write('\xc4')
39 |
40 | # def test_failure(self):
41 | # raise Exception("Ups, should be disabled")
42 |
43 |
44 | class EXMLTestResultTests(TestCase):
45 | def setUp(self):
46 | self.exml_result = EXMLTestResult(None, None, 1)
47 | self.exml_result.startTestRun()
48 | self.result_element = ET.SubElement(self.exml_result.tree, 'result')
49 |
50 | def test_non_ascii_traceback(self):
51 | try:
52 | self.explode_with_unicode_traceback()
53 | except ValueError:
54 | err = sys.exc_info()
55 | else:
56 | self.fail()
57 |
58 | self.exml_result._add_tb_to_test(TestCase('fail'), self.result_element, err)
59 |
60 | output = self.write_element(self.result_element)
61 |
62 | self.assert_(output)
63 |
64 | def test_non_ascii_message(self):
65 | try:
66 | self.explode_with_unicode_message()
67 | except ValueError:
68 | err = sys.exc_info()
69 | else:
70 | self.fail()
71 |
72 | self.exml_result._add_tb_to_test(TestCase('fail'), self.result_element, err)
73 |
74 | output = self.write_element(self.result_element)
75 |
76 | self.assert_(output)
77 |
78 | def write_element(self, element):
79 | # write out the element the way that our TestResult.dump_xml does.
80 | # (except not actually to disk.)
81 | tree = ET.ElementTree(element)
82 | output = io.BytesIO()
83 | # this bit blows up if components of the output are byte-strings with non-ascii content.
84 | tree.write(output, encoding='utf-8')
85 | output_bytes = output.getvalue()
86 | return output_bytes
87 |
88 | def explode_with_unicode_traceback(self):
89 | # The following will result in an ascii error message, but the traceback will contain the
90 | # full source line, including the comment's non-ascii characters.
91 | raise ValueError("dead") # "⚠ Not enough ☕"
92 |
93 | def explode_with_unicode_message(self):
94 | # This source code has only ascii, the exception has a non-ascii message.
95 | raise ValueError(u"\N{BIOHAZARD SIGN} Too much \N{HOT BEVERAGE}")
96 |
97 |
98 | class SeleniumTests(LiveServerTestCase):
99 | @classmethod
100 | def setUpClass(cls):
101 | cls.display = Display(visible=0, size=(1024, 768))
102 | cls.display.start()
103 | cls.selenium = webdriver.Firefox()
104 | super(SeleniumTests, cls).setUpClass()
105 |
106 | @classmethod
107 | def tearDownClass(cls):
108 | super(SeleniumTests, cls).tearDownClass()
109 | cls.selenium.quit()
110 | cls.display.stop()
111 |
112 | def test_login(self):
113 | self.selenium.get('%s%s' % (self.live_server_url, '/test_click/'))
114 | self.selenium.find_element_by_id("wm_click").click()
115 | self.assertEqual('Button clicked', self.selenium.find_element_by_id("wm_target").text)
116 |
--------------------------------------------------------------------------------
/tests/test_app/urls.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from django.conf.urls import url
3 | from django.views.generic.base import TemplateView
4 |
5 | urlpatterns = [
6 | url(r'^test_click/$', TemplateView.as_view(template_name='test_app/wm_test_click.html'),
7 | name='wm_test_click')
8 | ]
9 |
--------------------------------------------------------------------------------
/tests/test_app/views.py:
--------------------------------------------------------------------------------
1 | # Create your views here.
2 |
--------------------------------------------------------------------------------
/tests/test_app_dirs/__init__.py:
--------------------------------------------------------------------------------
1 | from test_app_dirs.not_for_coverage import one, two
--------------------------------------------------------------------------------
/tests/test_app_dirs/models/__init__.py:
--------------------------------------------------------------------------------
1 | from test_app_dirs.models.sample import TestDirModel # NOQA
--------------------------------------------------------------------------------
/tests/test_app_dirs/models/sample.py:
--------------------------------------------------------------------------------
1 | from django.db import models
2 |
3 |
4 | class TestDirModel(models.Model):
5 | test_text = models.CharField(max_length=250)
6 |
7 | class Meta:
8 | app_label = 'test_app_dirs'
--------------------------------------------------------------------------------
/tests/test_app_dirs/not_for_coverage/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmmbvnr/django-jenkins/f673c8ecded24eb56aeaef8d1c0caeb82208b595/tests/test_app_dirs/not_for_coverage/__init__.py
--------------------------------------------------------------------------------
/tests/test_app_dirs/not_for_coverage/one.py:
--------------------------------------------------------------------------------
1 | def test():
2 | print('Not for coverage')
--------------------------------------------------------------------------------
/tests/test_app_dirs/not_for_coverage/two.py:
--------------------------------------------------------------------------------
1 | def test():
2 | print('Not for coverage')
--------------------------------------------------------------------------------
/tests/test_app_dirs/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kmmbvnr/django-jenkins/f673c8ecded24eb56aeaef8d1c0caeb82208b595/tests/test_app_dirs/tests/__init__.py
--------------------------------------------------------------------------------
/tests/test_app_dirs/tests/test_discovery_dir_tests.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | from django.test import TestCase
3 |
4 |
5 | class DirDiscoveryTest(TestCase):
6 | def test_should_be_dicoverd(self):
7 | """
8 | Yep!
9 | """
10 |
--------------------------------------------------------------------------------
/tests/test_non_bound.py:
--------------------------------------------------------------------------------
1 | from django.test import TestCase
2 |
3 |
4 | class NonBoundTest(TestCase):
5 | def test_executed(self):
6 | """
7 | This test executed only if no --project-apps-tests option provided
8 | """
9 |
--------------------------------------------------------------------------------
/tox.ini:
--------------------------------------------------------------------------------
1 | [tox]
2 | envlist = py{27,34,35}-{dj18,dj19,dj110}
3 | skipsdist = True
4 |
5 | [testenv]
6 | basepython =
7 | py27: python2.7
8 | py34: python3.4
9 | py35: python3.5
10 | commands = python tests/manage.py {posargs:jenkins --enable-coverage --pep8-max-line-length=150 --liveserver=localhost:8090-8100 --output-dir=reports/{envname} --testrunner=tests.runner.CustomTestRunner --ok}
11 | deps =
12 | dj18: django==1.8.17
13 | dj19: django==1.9.12
14 | dj110: django==1.10.5
15 | pylint==1.6.5
16 | pylint-django==0.7.2
17 | coverage==4.3.4
18 | pyflakes==1.5.0
19 | pep8==1.7.0
20 | flake8==3.3.0
21 | selenium==3.0.2
22 | pyvirtualdisplay==0.2.1
23 | ipdb
24 | passenv=HOME
25 |
--------------------------------------------------------------------------------