Retire stackforge/striker
This commit is contained in:
parent
b986640bf1
commit
5b868e5857
14
.gitignore
vendored
14
.gitignore
vendored
@ -1,14 +0,0 @@
|
||||
/.DS_Store
|
||||
/.coverage
|
||||
/.tox
|
||||
/.venv
|
||||
/AUTHORS
|
||||
/ChangeLog
|
||||
/build
|
||||
/cov_html
|
||||
/dist
|
||||
*.egg-info
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
*.whl
|
176
LICENSE
176
LICENSE
@ -1,176 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
@ -1,2 +0,0 @@
|
||||
include requirements.txt test-requirements.txt README.rst run_tests.sh
|
||||
recursive-include tests *.py
|
30
README.rst
30
README.rst
@ -1,27 +1,7 @@
|
||||
=======
|
||||
Striker
|
||||
=======
|
||||
This project is no longer maintained.
|
||||
|
||||
Striker is a deployment package builder, capable of building packages
|
||||
in several different formats, running basic tests on those packages,
|
||||
and distributing them. It is a tool for managing the build and
|
||||
release lifecycle.
|
||||
The contents of this repository are still available in the Git source code
|
||||
management system. To see the contents of this repository before it reached
|
||||
its end of life, please check out the previous commit with
|
||||
"git checkout HEAD^1".
|
||||
|
||||
Packaging
|
||||
=========
|
||||
|
||||
Why another packaging tool? After all, the Python world already has
|
||||
eggs and wheels, and they work really well, right? Well, yes and no.
|
||||
A wheel (or the older egg) contains a single package and information
|
||||
about its dependencies. Installing a full project onto a system
|
||||
requires that several wheels be downloaded and installed, and we often
|
||||
have dependencies on the exact versions that are used--i.e., which
|
||||
exact versions have we performed acceptance tests against? Also, when
|
||||
you're talking about installing a package across several thousand
|
||||
machines, just downloading all those dependencies represents an
|
||||
enormous network load.
|
||||
|
||||
Striker is intended to help with this problem. The packages it builds
|
||||
include all of the dependencies into a single artifact, which can then
|
||||
be distributed to those several thousand systems in a much more
|
||||
scalable fashion, such as a peer-to-peer system like BitTorrent.
|
||||
|
136
doc/Makefile
136
doc/Makefile
@ -1,136 +0,0 @@
|
||||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||
|
||||
.PHONY: help clean html pdf dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest
|
||||
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " pdf to make pdf with rst2pdf"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
|
||||
clean:
|
||||
-rm -rf $(BUILDDIR)/*
|
||||
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
pdf:
|
||||
$(SPHINXBUILD) -b pdf $(ALLSPHINXOPTS) $(BUILDDIR)/pdf
|
||||
@echo
|
||||
@echo "Build finished. The PDFs are in $(BUILDDIR)/pdf."
|
||||
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/NebulaDocs.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/NebulaDocs.qhc"
|
||||
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/NebulaDocs"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/NebulaDocs"
|
||||
@echo "# devhelp"
|
||||
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
make -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
@ -1,6 +0,0 @@
|
||||
pbr>=0.6,!=0.7,<1.0
|
||||
cliff
|
||||
jsonschema
|
||||
PyYAML
|
||||
six
|
||||
stevedore
|
265
run_tests.sh
265
run_tests.sh
@ -1,265 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
########################## Various utility functions ##########################
|
||||
|
||||
# Canonicalize a directory name
|
||||
canon_dir () {
|
||||
(cd "$1" && pwd -P)
|
||||
}
|
||||
|
||||
# Extract the argument from "--foo=..." style arguments
|
||||
get_arg () {
|
||||
echo "$1" | sed 's/[^=]*=//'
|
||||
}
|
||||
|
||||
# Add a parameter to the set of parameters with which to invoke
|
||||
# nosetests
|
||||
add_params () {
|
||||
if [ x"${params}" = x ]; then
|
||||
params="$*"
|
||||
else
|
||||
params="${params} $*"
|
||||
fi
|
||||
}
|
||||
|
||||
# An alias for the Python interpreter from the virtual environment
|
||||
python () {
|
||||
${venv_path}/bin/python "$@"
|
||||
}
|
||||
|
||||
# An alias for pip from the virtual environment
|
||||
pip () {
|
||||
# Invoke using the python from the virtual environment; this works
|
||||
# around spaces being present in the "#!" line
|
||||
python ${venv_path}/bin/pip "$@"
|
||||
}
|
||||
|
||||
# An alias for pep8, using the virtual environment if requested
|
||||
run_pep8 () {
|
||||
if [ ${venv} = yes ]; then
|
||||
# Invoke using the python from the virtual environment; this
|
||||
# works around spaces being present in the "#!" line
|
||||
python ${venv_path}/bin/pep8 "$@"
|
||||
else
|
||||
pep8 "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# An alias for nosetests, using the virtual environment if requested
|
||||
run_nosetests () {
|
||||
if [ ${venv} = yes ]; then
|
||||
# Invoke using the python from the virtual environment; this
|
||||
# works around spaces being present in the "#!" line
|
||||
python ${venv_path}/bin/nosetests "$@"
|
||||
else
|
||||
nosetests "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Output a usage message
|
||||
usage () {
|
||||
cat >&2 <<EOF
|
||||
Usage: ${prog} [options] [<TESTS>]
|
||||
|
||||
Execute the Striker test suite.
|
||||
|
||||
Options:
|
||||
-h
|
||||
--help Outputs this help text.
|
||||
|
||||
-V
|
||||
--virtual-env
|
||||
Set up and use a virtual environment for testing.
|
||||
|
||||
-N
|
||||
--no-virtual-env
|
||||
Do not set up or use a virtual environment for testing.
|
||||
|
||||
-r
|
||||
--reset Resets the virtual environment prior to building it.
|
||||
|
||||
-p
|
||||
--pep8 Execute only the PEP8 compliance check.
|
||||
|
||||
-P
|
||||
--no-pep8 Do not execute the PEP8 compliance check.
|
||||
|
||||
-c
|
||||
--coverage Generate a coverage report
|
||||
|
||||
-H <DIR>
|
||||
--coverage-html=<DIR>
|
||||
Specify the directory to contain the HTML coverage
|
||||
report.
|
||||
|
||||
<TESTS> A list of test specifications for nosetests.
|
||||
EOF
|
||||
|
||||
exit ${1:-1}
|
||||
}
|
||||
|
||||
################################ Initialization ###############################
|
||||
|
||||
prog=`basename $0`
|
||||
dir=`dirname $0`
|
||||
dir=`canon_dir "${dir}"`
|
||||
|
||||
# Initialize parameters for invoking nosetests
|
||||
params=
|
||||
|
||||
# Initialize other variables
|
||||
venv=ask
|
||||
reset=false
|
||||
pep8=yes
|
||||
coverage=no
|
||||
cov_html=cov_html
|
||||
|
||||
############################## Process arguments ##############################
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
-h|--help)
|
||||
usage 0 2>&1
|
||||
;;
|
||||
-V|--virtual-env)
|
||||
venv=yes
|
||||
;;
|
||||
-N|--no-virtual-env)
|
||||
venv=no
|
||||
;;
|
||||
-r|--reset)
|
||||
reset=true
|
||||
;;
|
||||
-p|--pep8)
|
||||
pep8=only
|
||||
;;
|
||||
-P|--no-pep8)
|
||||
pep8=no
|
||||
;;
|
||||
-c|--coverage)
|
||||
coverage=yes
|
||||
;;
|
||||
-H|--coverage-html)
|
||||
shift
|
||||
cov_html="$1"
|
||||
;;
|
||||
--coverage-html=*)
|
||||
cov_html=`get_arg "$1"`
|
||||
;;
|
||||
--*)
|
||||
echo "Unrecognized option \"$1\"" >&2
|
||||
usage
|
||||
;;
|
||||
*)
|
||||
add_params "$1"
|
||||
;;
|
||||
esac
|
||||
|
||||
shift
|
||||
done
|
||||
|
||||
############################ Set up the environment ###########################
|
||||
|
||||
# Ask if we should use a virtual environment
|
||||
venv_path="${dir}/.venv"
|
||||
if [ ${venv} = ask ]; then
|
||||
if [ -d ${venv_path} ]; then
|
||||
venv=yes
|
||||
else
|
||||
echo -n "No virtual environment found; create one? (Y/n) "
|
||||
read use_venv
|
||||
if [ "x${use_venv}" = "xY" -o "x${use_venv}" = "xy" -o \
|
||||
"x${use_venv}" = "x" ]; then
|
||||
venv=yes
|
||||
else
|
||||
venv=no
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
# Set up the virtual environment if requested
|
||||
if [ ${venv} = yes ]; then
|
||||
# Reset the virtual environment if requested
|
||||
if [ ${reset} = true -a -d ${venv_path} ]; then
|
||||
echo "Forced reset of virtual environment"
|
||||
rm -rf ${venv_path}
|
||||
fi
|
||||
|
||||
# Now create the virtual environment
|
||||
if [ ! -d ${venv_path} ]; then
|
||||
echo "Creating virtual environment"
|
||||
virtualenv ${venv_path}
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to create virtual environment" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Installing/updating requirements in virtual environment"
|
||||
pip install -U -r ${dir}/requirements.txt -r ${dir}/test-requirements.txt
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to install/update requirements in virtual environment" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Installing striker setup in the virtual environment"
|
||||
python ${dir}/setup.py install
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Failed to install striker setup in virtual environment" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
export VIRTUAL_ENV=${venv_path}
|
||||
fi
|
||||
|
||||
export BASE_DIR=${dir}
|
||||
|
||||
################################ Run the tests ################################
|
||||
|
||||
errors=0
|
||||
if [ ${pep8} != only ]; then
|
||||
# Set up the options for nosetests
|
||||
options="-v"
|
||||
if [ ${coverage} = yes ]; then
|
||||
options="${options} --with-coverage --cover-branches"
|
||||
options="${options} --cover-package=striker"
|
||||
options="${options} --cover-html --cover-html-dir=${cov_html}"
|
||||
fi
|
||||
|
||||
# Need to restrict tests to just the test directory
|
||||
if [ x"${params}" = x ]; then
|
||||
params=tests
|
||||
fi
|
||||
|
||||
# Run nosetests
|
||||
echo
|
||||
echo "Testing Python code:"
|
||||
echo
|
||||
run_nosetests ${options} ${params}
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Tests on Striker failed" >&2
|
||||
errors=`expr ${errors} + 1`
|
||||
fi
|
||||
fi
|
||||
|
||||
# Run pep8
|
||||
if [ ${pep8} != no ]; then
|
||||
echo
|
||||
echo "Running PEP8 tests:"
|
||||
echo
|
||||
run_pep8 ${dir}/striker ${dir}/tests
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "Pep8 compliance test failed" >&2
|
||||
errors=`expr ${errors} + 1`
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ${errors} -gt 0 ]; then
|
||||
echo
|
||||
echo "Test failures encountered!" >&2
|
||||
else
|
||||
echo
|
||||
echo "Test suite successful!" >&2
|
||||
fi
|
||||
|
||||
exit ${errors}
|
28
setup.cfg
28
setup.cfg
@ -1,28 +0,0 @@
|
||||
[metadata]
|
||||
name = striker
|
||||
summary = Rackspace Task Runner
|
||||
description-file =
|
||||
README.rst
|
||||
author = Rackspace Hosting
|
||||
author-email = striker-dev@rackspace.com
|
||||
home-page = http://rackspace.com
|
||||
Development Status :: 5 Alpha
|
||||
Environment :: Development
|
||||
Intended Audience :: Information Technology
|
||||
Intended Audience :: System Administrators
|
||||
License :: OSI Approved :: Apache Software License
|
||||
Operating System :: POSIX :: Linux
|
||||
Programming Language :: Python
|
||||
Programming Language :: Python :: 2
|
||||
Programming Language :: Python :: 2.7
|
||||
Programming Language :: Python :: 2.6
|
||||
Programming Language :: Python :: 3
|
||||
Programming Language :: Python :: 3.3
|
||||
Programming Language :: Python :: 3.4
|
||||
|
||||
[files]
|
||||
packages =
|
||||
striker
|
||||
|
||||
[wheel]
|
||||
universal = 1
|
28
setup.py
28
setup.py
@ -1,28 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
||||
import setuptools
|
||||
|
||||
# In python < 2.7.4, a lazy loading of package `pbr` will break
|
||||
# setuptools if some other modules registered functions in `atexit`.
|
||||
# solution from: http://bugs.python.org/issue15881#msg170215
|
||||
try:
|
||||
import multiprocessing # noqa
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
setuptools.setup(
|
||||
setup_requires=['pbr'],
|
||||
pbr=True)
|
@ -1,14 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
@ -1,14 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
@ -1,14 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
@ -1,14 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
File diff suppressed because it is too large
Load Diff
@ -1,99 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
import six
|
||||
|
||||
|
||||
def canonicalize_path(cwd, path):
|
||||
"""
|
||||
Canonicalizes a path relative to a given working directory.
|
||||
|
||||
:param cwd: The working directory to interpret ``path`` relative
|
||||
to.
|
||||
:param path: The path to canonicalize. If relative, it will be
|
||||
interpreted relative to ``cwd``.
|
||||
|
||||
:returns: The absolute path.
|
||||
"""
|
||||
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.join(cwd, path)
|
||||
|
||||
return os.path.abspath(path)
|
||||
|
||||
|
||||
def backoff(max_tries):
|
||||
"""
|
||||
A generator to perform simplified exponential backoff. Yields up
|
||||
to the specified number of times, performing a ``time.sleep()``
|
||||
with an exponentially increasing sleep time (starting at 1 second)
|
||||
between each trial. Yields the (0-based) trial number.
|
||||
|
||||
:param max_tries: The maximum number of tries to attempt.
|
||||
"""
|
||||
|
||||
# How much time will we sleep next time?
|
||||
sleep = 1
|
||||
|
||||
for i in range(max_tries):
|
||||
# Yield the trial number
|
||||
yield i
|
||||
|
||||
# We've re-entered the loop; sleep, then increment the sleep
|
||||
# time
|
||||
time.sleep(sleep)
|
||||
sleep <<= 1
|
||||
|
||||
|
||||
def boolean(value, default=None):
|
||||
"""
|
||||
Convert a string value into a boolean. The values 'true', 't',
|
||||
'yes', 'y', and 'on', as well as non-zero integer values, are
|
||||
recognized as ``True``, while the values 'false', 'f', 'no', 'n',
|
||||
and 'off', as well as the integer value 0, are recognized as
|
||||
``False``. A ``ValueError`` is raised for other values unless the
|
||||
``default`` parameter is given, in which case it is returned.
|
||||
|
||||
:param value: The string value to be converted to boolean.
|
||||
:param default: If not ``None``, specifies the desired default
|
||||
value if the ``value`` is not one of the
|
||||
recognized values.
|
||||
|
||||
:returns: The boolean value derived from the string.
|
||||
"""
|
||||
|
||||
# Cover non-string case
|
||||
if not isinstance(value, six.string_types):
|
||||
return bool(value)
|
||||
|
||||
# Cover the integer case
|
||||
if value.isdigit():
|
||||
return bool(int(value))
|
||||
|
||||
# Check for recognized values
|
||||
tmp = value.lower()
|
||||
if tmp in ('true', 't', 'yes', 'y', 'on'):
|
||||
return True
|
||||
elif tmp in ('false', 'f', 'no', 'n', 'off'):
|
||||
return False
|
||||
|
||||
# Return the default value
|
||||
if default is not None:
|
||||
return default
|
||||
|
||||
raise ValueError('invalid boolean literal %r' % value)
|
@ -1,14 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
@ -1,123 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
||||
|
||||
from striker.core import environment
|
||||
|
||||
|
||||
class Context(object):
|
||||
"""
|
||||
Execution context. Objects of this class contain all the basic
|
||||
configuration data needed to perform a task.
|
||||
"""
|
||||
|
||||
def __init__(self, workspace, config, logger,
|
||||
debug=False, dry_run=False, **extras):
|
||||
"""
|
||||
Initialize a ``Context`` object.
|
||||
|
||||
:param workspace: The name of a temporary working directory.
|
||||
The directory must exist.
|
||||
:param config: An object containing configuration data. The
|
||||
object should support read-only attribute-style
|
||||
access to the configuration settings.
|
||||
:param logger: An object compatible with ``logging.Logger``.
|
||||
This will be used by all consumers of the
|
||||
``Context`` to emit logging information.
|
||||
:param debug: A boolean, defaulting to ``False``, indicating
|
||||
whether debugging mode is active.
|
||||
:param dry_run: A boolean, defaulting to ``False``, indicating
|
||||
whether permanent changes should be effected.
|
||||
This should be used to control whether files
|
||||
are uploaded, for instance.
|
||||
:param extras: Keyword arguments specifying additional data to
|
||||
be stored in the context. This could be, for
|
||||
instance, account data.
|
||||
"""
|
||||
|
||||
# Store basic context data
|
||||
self.workspace = workspace
|
||||
self.config = config
|
||||
self.logger = logger
|
||||
self.debug = debug
|
||||
self.dry_run = dry_run
|
||||
|
||||
# Extra data--things like accounts
|
||||
self._extras = extras
|
||||
|
||||
# Environment
|
||||
self._environ = None
|
||||
|
||||
def __getattr__(self, name):
|
||||
"""
|
||||
Provides access to the extra data specified to the constructor.
|
||||
|
||||
:param name: The name of the extra datum to retrieve.
|
||||
|
||||
:returns: The value of the extra datum.
|
||||
"""
|
||||
|
||||
if name not in self._extras:
|
||||
raise AttributeError("'%s' object has no attribute '%s'" %
|
||||
(self.__class__.__name__, name))
|
||||
|
||||
return self._extras[name]
|
||||
|
||||
@property
|
||||
def environ(self):
|
||||
"""
|
||||
Access the environment. The environment is a dictionary of
|
||||
environment variables, but it is also a callable that can be
|
||||
used to invoke shell commands.
|
||||
|
||||
:param cmd: The command to execute, as either a bare string or
|
||||
a list of arguments. If a string, it will be
|
||||
split into a list using ``shlex.split()``. Note
|
||||
that use of bare strings for this argument is
|
||||
discouraged.
|
||||
:param capture_output: If ``True``, standard input and output
|
||||
will be captured, and will be available
|
||||
in the result. Defaults to ``False``.
|
||||
Note that this is treated as implicitly
|
||||
``True`` if the ``retry`` parameter is
|
||||
provided.
|
||||
:param cwd: Gives an alternate working directory from which to
|
||||
run the command.
|
||||
:param do_raise: If ``True`` (the default), an execution
|
||||
failure will raise an exception.
|
||||
:param retry: If provided, must be a callable taking one
|
||||
argument. Will be called with an instance of
|
||||
``ExecResult``, and can return ``True`` to
|
||||
indicate that the call should be retried.
|
||||
Retries are performed with an exponential
|
||||
backoff controlled by ``max_tries``.
|
||||
:param max_tries: The maximum number of tries to perform
|
||||
before giving up, if ``retry`` is specified.
|
||||
Retries are performed with an exponential
|
||||
backoff: the first try is performed
|
||||
immediately, and subsequent tries occur
|
||||
after a sleep time that starts at one second
|
||||
and is doubled for each try.
|
||||
|
||||
:returns: An ``ExecResult`` object containing the results of
|
||||
the execution. If the return code was non-zero and
|
||||
``do_raise`` is ``True``, this is the object that
|
||||
will be raised.
|
||||
"""
|
||||
|
||||
# Construct the environment if necessary
|
||||
if self._environ is None:
|
||||
self._environ = environment.Environment(self.logger)
|
||||
|
||||
return self._environ
|
@ -1,301 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
||||
|
||||
import os
|
||||
import shlex
|
||||
import shutil
|
||||
import subprocess
|
||||
|
||||
import six
|
||||
|
||||
from striker.common import utils
|
||||
|
||||
|
||||
class ExecResult(Exception):
|
||||
"""
|
||||
Encapsulate the results of calling a command. This class extends
|
||||
``Exception`` so that it can be raised in the event of a command
|
||||
failure. The command executed is available in both list (``cmd``)
|
||||
and plain text (``cmd_text``) forms. If the command is executed
|
||||
with ``capture_output``, the standard output (``stdout``) and
|
||||
standard error (``stderr``) streams will also be available. The
|
||||
command return code is available in the ``return_code`` attribute.
|
||||
"""
|
||||
|
||||
def __init__(self, cmd, stdout, stderr, return_code):
|
||||
"""
|
||||
Initialize an ``ExecResult``.
|
||||
|
||||
:param cmd: The command, in list format.
|
||||
:param stdout: The standard output from the command execution.
|
||||
:param stderr: The standard error from the command execution.
|
||||
:param return_code: The return code from executing the
|
||||
command.
|
||||
"""
|
||||
|
||||
# Store all the data
|
||||
self.cmd = cmd
|
||||
self.stdout = stdout
|
||||
self.stderr = stderr
|
||||
self.return_code = return_code
|
||||
|
||||
# Form the command text
|
||||
comps = []
|
||||
for comp in cmd:
|
||||
# Determine if the component needs quoting
|
||||
if ' ' in comp or '"' in comp or "'" in comp:
|
||||
# Escape any double-quotes
|
||||
parts = comp.split('"')
|
||||
comp = '"%s"' % '\\"'.join(parts)
|
||||
|
||||
comps.append(comp)
|
||||
|
||||
# Save the command text
|
||||
self.cmd_text = ' '.join(comps)
|
||||
|
||||
# Formulate the message
|
||||
if return_code:
|
||||
msg = ("'%s' failed with return code %s" %
|
||||
(self.cmd_text, return_code))
|
||||
elif stderr:
|
||||
msg = "'%s' said: %s" % (self.cmd_text, stderr)
|
||||
elif stdout:
|
||||
msg = "'%s' said: %s" % (self.cmd_text, stdout)
|
||||
else:
|
||||
msg = "'%s' succeeded" % (self.cmd_text,)
|
||||
|
||||
# Initialize ourselves as an exception
|
||||
super(ExecResult, self).__init__(msg)
|
||||
|
||||
def __nonzero__(self):
|
||||
"""
|
||||
Allows conversion of an ``ExecResult`` to boolean, based on
|
||||
the command return code. If the return code was 0, the object
|
||||
will be considered ``True``; otherwise, the object will be
|
||||
considered ``False``.
|
||||
|
||||
:returns: ``True`` if the command succeeded, ``False``
|
||||
otherwise.
|
||||
"""
|
||||
|
||||
return not bool(self.return_code)
|
||||
__bool__ = __nonzero__
|
||||
|
||||
|
||||
class Environment(dict):
|
||||
"""
|
||||
Describes an environment that can be used for execution of
|
||||
subprocesses. Virtual environments can be created by calling the
|
||||
``create_venv()`` method, which returns an independent instance of
|
||||
``Environment``.
|
||||
"""
|
||||
|
||||
def __init__(self, logger, environ=None, cwd=None, venv_home=None):
|
||||
"""
|
||||
Initialize a new ``Environment``.
|
||||
|
||||
:param logger: An object compatible with ``logging.Logger``.
|
||||
This will be used to emit logging information.
|
||||
:param environ: A dictionary containing the environment
|
||||
variables. If not given, ``os.environ`` will
|
||||
be used.
|
||||
:param cwd: The working directory to use. If relative, will
|
||||
be interpreted relative to the current working
|
||||
directory. If not given, the current working
|
||||
directory will be used.
|
||||
:param venv_home: The home directory for the virtual
|
||||
environment.
|
||||
"""
|
||||
|
||||
super(Environment, self).__init__(environ or os.environ)
|
||||
|
||||
# Save the logger
|
||||
self.logger = logger
|
||||
|
||||
# Change to the desired working directory, then save the full
|
||||
# path to it
|
||||
self.cwd = os.getcwd()
|
||||
if cwd:
|
||||
self.chdir(cwd)
|
||||
|
||||
# Save the virtual environment home
|
||||
self.venv_home = venv_home
|
||||
|
||||
def __call__(self, cmd, capture_output=False, cwd=None, do_raise=True,
|
||||
retry=None, max_tries=5):
|
||||
"""
|
||||
Execute a command in the context of this environment.
|
||||
|
||||
:param cmd: The command to execute, as either a bare string or
|
||||
a list of arguments. If a string, it will be
|
||||
split into a list using ``shlex.split()``. Note
|
||||
that use of bare strings for this argument is
|
||||
discouraged.
|
||||
:param capture_output: If ``True``, standard input and output
|
||||
will be captured, and will be available
|
||||
in the result. Defaults to ``False``.
|
||||
Note that this is treated as implicitly
|
||||
``True`` if the ``retry`` parameter is
|
||||
provided.
|
||||
:param cwd: Gives an alternate working directory from which to
|
||||
run the command.
|
||||
:param do_raise: If ``True`` (the default), an execution
|
||||
failure will raise an exception.
|
||||
:param retry: If provided, must be a callable taking one
|
||||
argument. Will be called with an instance of
|
||||
``ExecResult``, and can return ``True`` to
|
||||
indicate that the call should be retried.
|
||||
Retries are performed with an exponential
|
||||
backoff controlled by ``max_tries``.
|
||||
:param max_tries: The maximum number of tries to perform
|
||||
before giving up, if ``retry`` is specified.
|
||||
Retries are performed with an exponential
|
||||
backoff: the first try is performed
|
||||
immediately, and subsequent tries occur
|
||||
after a sleep time that starts at one second
|
||||
and is doubled for each try.
|
||||
|
||||
:returns: An ``ExecResult`` object containing the results of
|
||||
the execution. If the return code was non-zero and
|
||||
``do_raise`` is ``True``, this is the object that
|
||||
will be raised.
|
||||
"""
|
||||
|
||||
# Sanity-check arguments
|
||||
if not retry or max_tries < 1:
|
||||
max_tries = 1
|
||||
|
||||
# Determine the working directory to use
|
||||
cwd = utils.canonicalize_path(self.cwd, cwd) if cwd else self.cwd
|
||||
|
||||
# Turn simple strings into lists of tokens
|
||||
if isinstance(cmd, six.string_types):
|
||||
self.logger.debug("Notice: splitting command string '%s'" %
|
||||
cmd)
|
||||
cmd = shlex.split(cmd)
|
||||
|
||||
self.logger.debug("Executing command: %r (cwd %s)" % (cmd, cwd))
|
||||
|
||||
# Prepare the keyword arguments for the Popen call
|
||||
kwargs = {
|
||||
'env': self,
|
||||
'cwd': cwd,
|
||||
'close_fds': True,
|
||||
}
|
||||
|
||||
# Set up stdout and stderr
|
||||
if capture_output or (retry and max_tries > 1):
|
||||
kwargs.update({
|
||||
'stdout': subprocess.PIPE,
|
||||
'stderr': subprocess.PIPE,
|
||||
})
|
||||
|
||||
# Perform the tries in a loop
|
||||
for trial in utils.backoff(max_tries):
|
||||
if trial:
|
||||
self.logger.warn("Failure caught; retrying command "
|
||||
"(try #%d)" % (trial + 1))
|
||||
|
||||
# Call the command
|
||||
child = subprocess.Popen(cmd, **kwargs)
|
||||
stdout, stderr = child.communicate()
|
||||
result = ExecResult(cmd, stdout, stderr, child.returncode)
|
||||
|
||||
# Check if we need to retry
|
||||
if retry and not result and retry(result):
|
||||
continue
|
||||
|
||||
break
|
||||
else:
|
||||
# Just log a warning that we couldn't retry
|
||||
self.logger.warn("Unable to retry: too many attempts")
|
||||
|
||||
# Raise an exception if requested
|
||||
if not result and do_raise:
|
||||
raise result
|
||||
|
||||
return result
|
||||
|
||||
def chdir(self, path):
|
||||
"""
|
||||
Change the working directory.
|
||||
|
||||
:param path: The path to change to. If relative, will be
|
||||
interpreted relative to the current working
|
||||
directory.
|
||||
|
||||
:returns: The new working directory.
|
||||
"""
|
||||
|
||||
self.cwd = utils.canonicalize_path(self.cwd, path)
|
||||
|
||||
return self.cwd
|
||||
|
||||
def create_venv(self, path, rebuild=False, **kwargs):
|
||||
"""
|
||||
Create a new, bare virtual environment rooted at the given
|
||||
directory. No packages will be installed, except what
|
||||
``virtualenv`` installs. Returns a new ``Environment`` set up
|
||||
for the new virtual environment, with the working directory
|
||||
set to be the same as the virtual environment directory. Any
|
||||
keyword arguments will override system environment variables
|
||||
in the new ``Environment`` object.
|
||||
|
||||
:param path: The path to create the virtual environment in.
|
||||
If relative, will be interpreted relative to the
|
||||
current working directory.
|
||||
:param rebuild: If ``True``, the virtual environment will be
|
||||
rebuilt even if it already exists. If
|
||||
``False`` (the default), the virtual
|
||||
environment will only be rebuilt if it doesn't
|
||||
already exist.
|
||||
:returns: A new ``Environment`` object.
|
||||
"""
|
||||
|
||||
# Determine the new virtual environment path
|
||||
path = utils.canonicalize_path(self.cwd, path)
|
||||
|
||||
self.logger.debug("Preparing virtual environment %s" % path)
|
||||
|
||||
# Check if we need to rebuild the virtual environment
|
||||
if os.path.exists(path):
|
||||
if rebuild:
|
||||
# Blow away the old tree
|
||||
self.logger.info("Destroying old virtual environment %s" %
|
||||
path)
|
||||
shutil.rmtree(path)
|
||||
else:
|
||||
self.logger.info("Using existing virtual environment %s" %
|
||||
path)
|
||||
else:
|
||||
# We'll need to create it
|
||||
rebuild = True
|
||||
|
||||
# Create the new virtual environment
|
||||
if rebuild:
|
||||
self.logger.info("Creating virtual environment %s" % path)
|
||||
self(['virtualenv', path])
|
||||
|
||||
# Set up the environment variables that are needed
|
||||
kwargs.setdefault('VIRTUAL_ENV', path)
|
||||
bindir = os.path.join(path, 'bin')
|
||||
kwargs.setdefault('PATH', '%s%s%s' %
|
||||
(bindir, os.pathsep, self['PATH']))
|
||||
|
||||
# Set up and return the new Environment
|
||||
new_env = self.__class__(self.logger, environ=self, cwd=path,
|
||||
venv_home=path)
|
||||
new_env.update(kwargs)
|
||||
return new_env
|
@ -1,6 +0,0 @@
|
||||
coverage
|
||||
mock
|
||||
nose
|
||||
pep8
|
||||
sphinx>=1.1.2,!=1.2.0,<1.3
|
||||
tox
|
@ -1,121 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
||||
|
||||
import six
|
||||
|
||||
|
||||
class TestException(Exception):
|
||||
"""
|
||||
An exception for the use of tests.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def fake_join(a, *p):
|
||||
"""
|
||||
Lifted from the POSIX implementation of os.path, for testing
|
||||
purposes.
|
||||
"""
|
||||
|
||||
path = a
|
||||
for b in p:
|
||||
if b.startswith('/'):
|
||||
path = b
|
||||
elif path == '' or path.endswith('/'):
|
||||
path += b
|
||||
else:
|
||||
path += '/' + b
|
||||
return path
|
||||
|
||||
|
||||
def fake_isabs(s):
|
||||
"""
|
||||
Lifted from the POSIX implementation of os.path, for testing
|
||||
purposes.
|
||||
"""
|
||||
|
||||
return s.startswith('/')
|
||||
|
||||
|
||||
def fake_abspath(path):
|
||||
"""
|
||||
Lifted from the POSIX implementation of os.path, for testing
|
||||
purposes.
|
||||
"""
|
||||
|
||||
if not fake_isabs(path):
|
||||
if six.PY2 and isinstance(path, unicode):
|
||||
cwd = os.getcwdu()
|
||||
elif six.PY3 and isinstance(path, bytes):
|
||||
cwd = os.getcwdb()
|
||||
else:
|
||||
cwd = os.getcwd()
|
||||
path = fake_join(cwd, path)
|
||||
return fake_normpath(path)
|
||||
|
||||
|
||||
def fake_normpath(path):
|
||||
"""
|
||||
Lifted from the POSIX implementation of os.path, for testing
|
||||
purposes.
|
||||
"""
|
||||
|
||||
if six.PY2 and isinstance(path, unicode):
|
||||
sep = u'/'
|
||||
empty = u''
|
||||
dot = u'.'
|
||||
dotdot = u'..'
|
||||
elif six.PY3 and isinstance(path, bytes):
|
||||
sep = b'/'
|
||||
empty = b''
|
||||
dot = b'.'
|
||||
dotdot = b'..'
|
||||
else:
|
||||
sep = '/'
|
||||
empty = ''
|
||||
dot = '.'
|
||||
dotdot = '..'
|
||||
|
||||
if path == empty:
|
||||
return dot
|
||||
|
||||
initial_slashes = path.startswith(sep)
|
||||
|
||||
# POSIX allows one or two initial slashes, but treats three or more
|
||||
# as single slash.
|
||||
if (initial_slashes and
|
||||
path.startswith(sep * 2) and not path.startswith(sep * 3)):
|
||||
initial_slashes = 2
|
||||
|
||||
comps = path.split(sep)
|
||||
new_comps = []
|
||||
for comp in comps:
|
||||
if comp in (empty, dot):
|
||||
continue
|
||||
|
||||
if (comp != dotdot or (not initial_slashes and not new_comps) or
|
||||
(new_comps and new_comps[-1] == dotdot)):
|
||||
new_comps.append(comp)
|
||||
elif new_comps:
|
||||
new_comps.pop()
|
||||
|
||||
comps = new_comps
|
||||
path = sep.join(comps)
|
||||
|
||||
if initial_slashes:
|
||||
path = sep * initial_slashes + path
|
||||
|
||||
return path or dot
|
@ -1,14 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
@ -1,14 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
@ -1,14 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
@ -1,14 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
@ -1,14 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
@ -1,14 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
@ -1,14 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
File diff suppressed because it is too large
Load Diff
@ -1,123 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
||||
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
from striker.common import utils
|
||||
|
||||
import tests
|
||||
|
||||
|
||||
class CanonicalizePathTest(unittest.TestCase):
|
||||
@mock.patch('os.path.isabs', tests.fake_isabs)
|
||||
@mock.patch('os.path.join', tests.fake_join)
|
||||
@mock.patch('os.path.abspath', tests.fake_abspath)
|
||||
def test_absolute(self):
|
||||
result = utils.canonicalize_path('/foo/bar', '/bar/baz')
|
||||
|
||||
self.assertEqual(result, '/bar/baz')
|
||||
|
||||
@mock.patch('os.path.isabs', tests.fake_isabs)
|
||||
@mock.patch('os.path.join', tests.fake_join)
|
||||
@mock.patch('os.path.abspath', tests.fake_abspath)
|
||||
def test_relative(self):
|
||||
result = utils.canonicalize_path('/foo/bar', 'bar/baz')
|
||||
|
||||
self.assertEqual(result, '/foo/bar/bar/baz')
|
||||
|
||||
@mock.patch('os.path.isabs', tests.fake_isabs)
|
||||
@mock.patch('os.path.join', tests.fake_join)
|
||||
@mock.patch('os.path.abspath', tests.fake_abspath)
|
||||
def test_relative_with_cwd(self):
|
||||
result = utils.canonicalize_path('/foo/bar', './baz')
|
||||
|
||||
self.assertEqual(result, '/foo/bar/baz')
|
||||
|
||||
@mock.patch('os.path.isabs', tests.fake_isabs)
|
||||
@mock.patch('os.path.join', tests.fake_join)
|
||||
@mock.patch('os.path.abspath', tests.fake_abspath)
|
||||
def test_relative_with_parent(self):
|
||||
result = utils.canonicalize_path('/foo/bar', '../baz')
|
||||
|
||||
self.assertEqual(result, '/foo/baz')
|
||||
|
||||
|
||||
class BackoffTest(unittest.TestCase):
|
||||
@mock.patch('time.sleep')
|
||||
def test_backoff(self, mock_sleep):
|
||||
max_tries = 5
|
||||
|
||||
for i, trial in enumerate(utils.backoff(max_tries)):
|
||||
self.assertEqual(i, trial)
|
||||
|
||||
if i:
|
||||
mock_sleep.assert_called_once_with(1 << (i - 1))
|
||||
else:
|
||||
self.assertFalse(mock_sleep.called)
|
||||
|
||||
mock_sleep.reset_mock()
|
||||
|
||||
self.assertEqual(i, max_tries - 1)
|
||||
|
||||
|
||||
class BooleanTest(unittest.TestCase):
|
||||
truth_table = [
|
||||
('TrUe', True),
|
||||
('t', True),
|
||||
('T', True),
|
||||
('yEs', True),
|
||||
('y', True),
|
||||
('Y', True),
|
||||
('oN', True),
|
||||
('1', True),
|
||||
('120', True),
|
||||
('FaLsE', False),
|
||||
('f', False),
|
||||
('F', False),
|
||||
('nO', False),
|
||||
('n', False),
|
||||
('N', False),
|
||||
('oFf', False),
|
||||
('0', False),
|
||||
('000', False),
|
||||
('other', None),
|
||||
(True, True),
|
||||
(False, False),
|
||||
(1, True),
|
||||
(0, False),
|
||||
]
|
||||
|
||||
def test_with_raise(self):
|
||||
for value, expected in self.truth_table:
|
||||
if expected is None:
|
||||
self.assertRaises(ValueError, utils.boolean, value)
|
||||
else:
|
||||
self.assertEqual(expected, utils.boolean(value))
|
||||
|
||||
def test_default_false(self):
|
||||
for value, expected in self.truth_table:
|
||||
if expected is None:
|
||||
expected = False
|
||||
|
||||
self.assertEqual(expected, utils.boolean(value, False))
|
||||
|
||||
def test_default_true(self):
|
||||
for value, expected in self.truth_table:
|
||||
if expected is None:
|
||||
expected = True
|
||||
|
||||
self.assertEqual(expected, utils.boolean(value, True))
|
@ -1,14 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
@ -1,79 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
||||
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
from striker.core import context
|
||||
from striker.core import environment
|
||||
|
||||
|
||||
class ContextTest(unittest.TestCase):
|
||||
def test_init_base(self):
|
||||
ctxt = context.Context('/path/to/workspace', 'config', 'logger')
|
||||
|
||||
self.assertEqual(ctxt.workspace, '/path/to/workspace')
|
||||
self.assertEqual(ctxt.config, 'config')
|
||||
self.assertEqual(ctxt.logger, 'logger')
|
||||
self.assertEqual(ctxt.debug, False)
|
||||
self.assertEqual(ctxt.dry_run, False)
|
||||
self.assertEqual(ctxt._extras, {})
|
||||
self.assertEqual(ctxt._environ, None)
|
||||
|
||||
def test_init_alt(self):
|
||||
ctxt = context.Context('/path/to/workspace', 'config', 'logger',
|
||||
debug=True, dry_run=True, accounts='accounts',
|
||||
other='other')
|
||||
|
||||
self.assertEqual(ctxt.workspace, '/path/to/workspace')
|
||||
self.assertEqual(ctxt.config, 'config')
|
||||
self.assertEqual(ctxt.logger, 'logger')
|
||||
self.assertEqual(ctxt.debug, True)
|
||||
self.assertEqual(ctxt.dry_run, True)
|
||||
self.assertEqual(ctxt._extras, {
|
||||
'accounts': 'accounts',
|
||||
'other': 'other',
|
||||
})
|
||||
self.assertEqual(ctxt._environ, None)
|
||||
|
||||
def test_getattr_exists(self):
|
||||
ctxt = context.Context('/path/to/workspace', 'config', 'logger',
|
||||
attr='value')
|
||||
|
||||
self.assertEqual(ctxt.attr, 'value')
|
||||
|
||||
def test_getattr_noexist(self):
|
||||
ctxt = context.Context('/path/to/workspace', 'config', 'logger',
|
||||
attr='value')
|
||||
|
||||
self.assertRaises(AttributeError, lambda: ctxt.other)
|
||||
|
||||
@mock.patch.object(environment, 'Environment', return_value='environ')
|
||||
def test_environ_cached(self, mock_Environment):
|
||||
ctxt = context.Context('/path/to/workspace', 'config', 'logger')
|
||||
ctxt._environ = 'cached'
|
||||
|
||||
self.assertEqual(ctxt.environ, 'cached')
|
||||
self.assertEqual(ctxt._environ, 'cached')
|
||||
self.assertFalse(mock_Environment.called)
|
||||
|
||||
@mock.patch.object(environment, 'Environment', return_value='environ')
|
||||
def test_environ_uncached(self, mock_Environment):
|
||||
ctxt = context.Context('/path/to/workspace', 'config', 'logger')
|
||||
|
||||
self.assertEqual(ctxt.environ, 'environ')
|
||||
self.assertEqual(ctxt._environ, 'environ')
|
||||
mock_Environment.assert_called_once_with('logger')
|
@ -1,667 +0,0 @@
|
||||
# Copyright 2014 Rackspace
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing,
|
||||
# software distributed under the License is distributed on an "AS
|
||||
# IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
|
||||
# express or implied. See the License for the specific language
|
||||
# governing permissions and limitations under the License.
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
from striker.common import utils
|
||||
from striker.core import environment
|
||||
|
||||
import tests
|
||||
|
||||
|
||||
class ExecResultTest(unittest.TestCase):
|
||||
def test_init_success(self):
|
||||
cmd = ['arg1', 'arg2 space', 'arg3"double', "arg4'single", 'arg5']
|
||||
cmd_text = 'arg1 "arg2 space" "arg3\\"double" "arg4\'single" arg5'
|
||||
result = environment.ExecResult(cmd, None, None, 0)
|
||||
|
||||
self.assertEqual(result.cmd, cmd)
|
||||
self.assertEqual(result.cmd_text, cmd_text)
|
||||
self.assertEqual(result.stdout, None)
|
||||
self.assertEqual(result.stderr, None)
|
||||
self.assertEqual(result.return_code, 0)
|
||||
self.assertEqual(str(result), "'%s' succeeded" % cmd_text)
|
||||
|
||||
def test_init_stdout(self):
|
||||
cmd = ['arg1', 'arg2 space', 'arg3"double', "arg4'single", 'arg5']
|
||||
cmd_text = 'arg1 "arg2 space" "arg3\\"double" "arg4\'single" arg5'
|
||||
result = environment.ExecResult(cmd, 'output', None, 0)
|
||||
|
||||
self.assertEqual(result.cmd, cmd)
|
||||
self.assertEqual(result.cmd_text, cmd_text)
|
||||
self.assertEqual(result.stdout, 'output')
|
||||
self.assertEqual(result.stderr, None)
|
||||
self.assertEqual(result.return_code, 0)
|
||||
self.assertEqual(str(result), "'%s' said: output" % cmd_text)
|
||||
|
||||
def test_init_stderr(self):
|
||||
cmd = ['arg1', 'arg2 space', 'arg3"double', "arg4'single", 'arg5']
|
||||
cmd_text = 'arg1 "arg2 space" "arg3\\"double" "arg4\'single" arg5'
|
||||
result = environment.ExecResult(cmd, 'output', 'error', 0)
|
||||
|
||||
self.assertEqual(result.cmd, cmd)
|
||||
self.assertEqual(result.cmd_text, cmd_text)
|
||||
self.assertEqual(result.stdout, 'output')
|
||||
self.assertEqual(result.stderr, 'error')
|
||||
self.assertEqual(result.return_code, 0)
|
||||
self.assertEqual(str(result), "'%s' said: error" % cmd_text)
|
||||
|
||||
def test_init_failure(self):
|
||||
cmd = ['arg1', 'arg2 space', 'arg3"double', "arg4'single", 'arg5']
|
||||
cmd_text = 'arg1 "arg2 space" "arg3\\"double" "arg4\'single" arg5'
|
||||
result = environment.ExecResult(cmd, 'output', 'error', 5)
|
||||
|
||||
self.assertEqual(result.cmd, cmd)
|
||||
self.assertEqual(result.cmd_text, cmd_text)
|
||||
self.assertEqual(result.stdout, 'output')
|
||||
self.assertEqual(result.stderr, 'error')
|
||||
self.assertEqual(result.return_code, 5)
|
||||
self.assertEqual(str(result), "'%s' failed with return code 5" %
|
||||
cmd_text)
|
||||
|
||||
def test_true(self):
|
||||
result = environment.ExecResult(['cmd'], None, None, 0)
|
||||
|
||||
self.assertTrue(result)
|
||||
|
||||
def test_false(self):
|
||||
result = environment.ExecResult(['cmd'], None, None, 1)
|
||||
|
||||
self.assertFalse(result)
|
||||
|
||||
|
||||
class EnvironmentTest(unittest.TestCase):
|
||||
@mock.patch.dict(os.environ, clear=True, TEST_VAR1='1', TEST_VAR2='2')
|
||||
@mock.patch.object(os, 'getcwd', return_value='/some/path')
|
||||
@mock.patch.object(environment.Environment, 'chdir')
|
||||
def test_init_base(self, mock_chdir, mock_getcwd):
|
||||
env = environment.Environment('logger')
|
||||
|
||||
self.assertEqual(env, {'TEST_VAR1': '1', 'TEST_VAR2': '2'})
|
||||
self.assertEqual(env.logger, 'logger')
|
||||
self.assertEqual(env.cwd, '/some/path')
|
||||
self.assertEqual(env.venv_home, None)
|
||||
self.assertFalse(mock_chdir.called)
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True, TEST_VAR1='1', TEST_VAR2='2')
|
||||
@mock.patch.object(os, 'getcwd', return_value='/some/path')
|
||||
@mock.patch.object(environment.Environment, 'chdir')
|
||||
def test_init_alt(self, mock_chdir, mock_getcwd):
|
||||
environ = {
|
||||
'TEST_VAR3': '3',
|
||||
'TEST_VAR4': '4',
|
||||
}
|
||||
env = environment.Environment('logger', environ, '/other/path',
|
||||
'/venv/home')
|
||||
|
||||
self.assertEqual(env, environ)
|
||||
self.assertEqual(env.logger, 'logger')
|
||||
self.assertEqual(env.cwd, '/some/path')
|
||||
self.assertEqual(env.venv_home, '/venv/home')
|
||||
mock_chdir.assert_called_once_with('/other/path')
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True, TEST_VAR1='1', TEST_VAR2='2')
|
||||
@mock.patch.object(os.path, 'join', tests.fake_join)
|
||||
@mock.patch.object(os, 'pathsep', ':')
|
||||
@mock.patch.object(os, 'getcwd', return_value='/some/path')
|
||||
@mock.patch.object(environment.Environment, 'chdir')
|
||||
@mock.patch.object(utils, 'canonicalize_path', return_value='/canon/path')
|
||||
@mock.patch.object(utils, 'backoff', return_value=[0])
|
||||
@mock.patch.object(subprocess, 'Popen', return_value=mock.Mock(**{
|
||||
'returncode': 0,
|
||||
'communicate.return_value': (None, None),
|
||||
}))
|
||||
def test_call_basic(self, mock_Popen, mock_backoff, mock_canonicalize_path,
|
||||
mock_chdir, mock_getcwd):
|
||||
logger = mock.Mock()
|
||||
env = environment.Environment(logger)
|
||||
|
||||
result = env(['test', 'one', 'two'])
|
||||
|
||||
self.assertEqual(result.cmd, ['test', 'one', 'two'])
|
||||
self.assertEqual(result.stdout, None)
|
||||
self.assertEqual(result.stderr, None)
|
||||
self.assertEqual(result.return_code, 0)
|
||||
self.assertFalse(mock_canonicalize_path.called)
|
||||
mock_backoff.assert_called_once_with(1)
|
||||
mock_Popen.assert_called_once_with(
|
||||
['test', 'one', 'two'], env=env, cwd='/some/path', close_fds=True)
|
||||
logger.assert_has_calls([
|
||||
mock.call.debug(
|
||||
"Executing command: ['test', 'one', 'two'] (cwd /some/path)"),
|
||||
])
|
||||
self.assertEqual(len(logger.method_calls), 1)
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True, TEST_VAR1='1', TEST_VAR2='2')
|
||||
@mock.patch.object(os, 'getcwd', return_value='/some/path')
|
||||
@mock.patch.object(environment.Environment, 'chdir')
|
||||
@mock.patch.object(utils, 'canonicalize_path', return_value='/canon/path')
|
||||
@mock.patch.object(utils, 'backoff', return_value=[0])
|
||||
@mock.patch.object(subprocess, 'Popen', return_value=mock.Mock(**{
|
||||
'returncode': 0,
|
||||
'communicate.return_value': (None, None),
|
||||
}))
|
||||
def test_call_string(self, mock_Popen, mock_backoff,
|
||||
mock_canonicalize_path, mock_chdir, mock_getcwd):
|
||||
logger = mock.Mock()
|
||||
env = environment.Environment(logger)
|
||||
|
||||
result = env("test one two")
|
||||
|
||||
self.assertEqual(result.cmd, ['test', 'one', 'two'])
|
||||
self.assertEqual(result.stdout, None)
|
||||
self.assertEqual(result.stderr, None)
|
||||
self.assertEqual(result.return_code, 0)
|
||||
self.assertFalse(mock_canonicalize_path.called)
|
||||
mock_backoff.assert_called_once_with(1)
|
||||
mock_Popen.assert_called_once_with(
|
||||
['test', 'one', 'two'], env=env, cwd='/some/path', close_fds=True)
|
||||
logger.assert_has_calls([
|
||||
mock.call.debug(
|
||||
"Notice: splitting command string 'test one two'"),
|
||||
mock.call.debug(
|
||||
"Executing command: ['test', 'one', 'two'] (cwd /some/path)"),
|
||||
])
|
||||
self.assertEqual(len(logger.method_calls), 2)
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True, TEST_VAR1='1', TEST_VAR2='2')
|
||||
@mock.patch.object(os, 'getcwd', return_value='/some/path')
|
||||
@mock.patch.object(environment.Environment, 'chdir')
|
||||
@mock.patch.object(utils, 'canonicalize_path', return_value='/canon/path')
|
||||
@mock.patch.object(utils, 'backoff', return_value=[0])
|
||||
@mock.patch.object(subprocess, 'Popen', return_value=mock.Mock(**{
|
||||
'returncode': 0,
|
||||
'communicate.return_value': (None, None),
|
||||
}))
|
||||
def test_call_cwd(self, mock_Popen, mock_backoff, mock_canonicalize_path,
|
||||
mock_chdir, mock_getcwd):
|
||||
logger = mock.Mock()
|
||||
env = environment.Environment(logger)
|
||||
|
||||
result = env(['test', 'one', 'two'], cwd='/other/path')
|
||||
|
||||
self.assertEqual(result.cmd, ['test', 'one', 'two'])
|
||||
self.assertEqual(result.stdout, None)
|
||||
self.assertEqual(result.stderr, None)
|
||||
self.assertEqual(result.return_code, 0)
|
||||
mock_canonicalize_path.assert_called_once_with(
|
||||
'/some/path', '/other/path')
|
||||
mock_backoff.assert_called_once_with(1)
|
||||
mock_Popen.assert_called_once_with(
|
||||
['test', 'one', 'two'], env=env, cwd='/canon/path', close_fds=True)
|
||||
logger.assert_has_calls([
|
||||
mock.call.debug(
|
||||
"Executing command: ['test', 'one', 'two'] (cwd /canon/path)"),
|
||||
])
|
||||
self.assertEqual(len(logger.method_calls), 1)
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True, TEST_VAR1='1', TEST_VAR2='2')
|
||||
@mock.patch.object(os, 'getcwd', return_value='/some/path')
|
||||
@mock.patch.object(environment.Environment, 'chdir')
|
||||
@mock.patch.object(utils, 'canonicalize_path', return_value='/canon/path')
|
||||
@mock.patch.object(utils, 'backoff', return_value=[0])
|
||||
@mock.patch.object(subprocess, 'Popen', return_value=mock.Mock(**{
|
||||
'returncode': 0,
|
||||
'communicate.return_value': ('output', 'error'),
|
||||
}))
|
||||
def test_call_capture(self, mock_Popen, mock_backoff,
|
||||
mock_canonicalize_path, mock_chdir, mock_getcwd):
|
||||
logger = mock.Mock()
|
||||
env = environment.Environment(logger)
|
||||
|
||||
result = env(['test', 'one', 'two'], capture_output=True)
|
||||
|
||||
self.assertEqual(result.cmd, ['test', 'one', 'two'])
|
||||
self.assertEqual(result.stdout, 'output')
|
||||
self.assertEqual(result.stderr, 'error')
|
||||
self.assertEqual(result.return_code, 0)
|
||||
self.assertFalse(mock_canonicalize_path.called)
|
||||
mock_backoff.assert_called_once_with(1)
|
||||
mock_Popen.assert_called_once_with(
|
||||
['test', 'one', 'two'], env=env, cwd='/some/path', close_fds=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
logger.assert_has_calls([
|
||||
mock.call.debug(
|
||||
"Executing command: ['test', 'one', 'two'] (cwd /some/path)"),
|
||||
])
|
||||
self.assertEqual(len(logger.method_calls), 1)
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True, TEST_VAR1='1', TEST_VAR2='2')
|
||||
@mock.patch.object(os, 'getcwd', return_value='/some/path')
|
||||
@mock.patch.object(environment.Environment, 'chdir')
|
||||
@mock.patch.object(utils, 'canonicalize_path', return_value='/canon/path')
|
||||
@mock.patch.object(utils, 'backoff', return_value=[0])
|
||||
@mock.patch.object(subprocess, 'Popen', return_value=mock.Mock(**{
|
||||
'returncode': 1,
|
||||
'communicate.return_value': (None, None),
|
||||
}))
|
||||
def test_call_failure_raise(self, mock_Popen, mock_backoff,
|
||||
mock_canonicalize_path, mock_chdir,
|
||||
mock_getcwd):
|
||||
logger = mock.Mock()
|
||||
env = environment.Environment(logger)
|
||||
|
||||
try:
|
||||
result = env(['test', 'one', 'two'])
|
||||
except environment.ExecResult as exc:
|
||||
self.assertEqual(exc.cmd, ['test', 'one', 'two'])
|
||||
self.assertEqual(exc.stdout, None)
|
||||
self.assertEqual(exc.stderr, None)
|
||||
self.assertEqual(exc.return_code, 1)
|
||||
else:
|
||||
self.fail("Expected ExecResult to be raised")
|
||||
|
||||
self.assertFalse(mock_canonicalize_path.called)
|
||||
mock_backoff.assert_called_once_with(1)
|
||||
mock_Popen.assert_called_once_with(
|
||||
['test', 'one', 'two'], env=env, cwd='/some/path', close_fds=True)
|
||||
logger.assert_has_calls([
|
||||
mock.call.debug(
|
||||
"Executing command: ['test', 'one', 'two'] (cwd /some/path)"),
|
||||
])
|
||||
self.assertEqual(len(logger.method_calls), 1)
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True, TEST_VAR1='1', TEST_VAR2='2')
|
||||
@mock.patch.object(os, 'getcwd', return_value='/some/path')
|
||||
@mock.patch.object(environment.Environment, 'chdir')
|
||||
@mock.patch.object(utils, 'canonicalize_path', return_value='/canon/path')
|
||||
@mock.patch.object(utils, 'backoff', return_value=[0])
|
||||
@mock.patch.object(subprocess, 'Popen', return_value=mock.Mock(**{
|
||||
'returncode': 1,
|
||||
'communicate.return_value': (None, None),
|
||||
}))
|
||||
def test_call_failure_noraise(self, mock_Popen, mock_backoff,
|
||||
mock_canonicalize_path, mock_chdir,
|
||||
mock_getcwd):
|
||||
logger = mock.Mock()
|
||||
env = environment.Environment(logger)
|
||||
|
||||
result = env(['test', 'one', 'two'], do_raise=False)
|
||||
|
||||
self.assertEqual(result.cmd, ['test', 'one', 'two'])
|
||||
self.assertEqual(result.stdout, None)
|
||||
self.assertEqual(result.stderr, None)
|
||||
self.assertEqual(result.return_code, 1)
|
||||
self.assertFalse(mock_canonicalize_path.called)
|
||||
mock_backoff.assert_called_once_with(1)
|
||||
mock_Popen.assert_called_once_with(
|
||||
['test', 'one', 'two'], env=env, cwd='/some/path', close_fds=True)
|
||||
logger.assert_has_calls([
|
||||
mock.call.debug(
|
||||
"Executing command: ['test', 'one', 'two'] (cwd /some/path)"),
|
||||
])
|
||||
self.assertEqual(len(logger.method_calls), 1)
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True, TEST_VAR1='1', TEST_VAR2='2')
|
||||
@mock.patch.object(os, 'getcwd', return_value='/some/path')
|
||||
@mock.patch.object(environment.Environment, 'chdir')
|
||||
@mock.patch.object(utils, 'canonicalize_path', return_value='/canon/path')
|
||||
@mock.patch.object(utils, 'backoff', return_value=[0])
|
||||
@mock.patch.object(subprocess, 'Popen', return_value=mock.Mock(**{
|
||||
'returncode': 0,
|
||||
'communicate.return_value': ('output', 'error'),
|
||||
}))
|
||||
def test_call_retry_success(self, mock_Popen, mock_backoff,
|
||||
mock_canonicalize_path, mock_chdir,
|
||||
mock_getcwd):
|
||||
logger = mock.Mock()
|
||||
env = environment.Environment(logger)
|
||||
retry = mock.Mock(return_value=True)
|
||||
|
||||
result = env(['test', 'one', 'two'], retry=retry)
|
||||
|
||||
self.assertEqual(result.cmd, ['test', 'one', 'two'])
|
||||
self.assertEqual(result.stdout, 'output')
|
||||
self.assertEqual(result.stderr, 'error')
|
||||
self.assertEqual(result.return_code, 0)
|
||||
self.assertFalse(mock_canonicalize_path.called)
|
||||
mock_backoff.assert_called_once_with(5)
|
||||
mock_Popen.assert_called_once_with(
|
||||
['test', 'one', 'two'], env=env, cwd='/some/path', close_fds=True,
|
||||
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
logger.assert_has_calls([
|
||||
mock.call.debug(
|
||||
"Executing command: ['test', 'one', 'two'] (cwd /some/path)"),
|
||||
])
|
||||
self.assertEqual(len(logger.method_calls), 1)
|
||||
self.assertFalse(retry.called)
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True, TEST_VAR1='1', TEST_VAR2='2')
|
||||
@mock.patch.object(os, 'getcwd', return_value='/some/path')
|
||||
@mock.patch.object(environment.Environment, 'chdir')
|
||||
@mock.patch.object(utils, 'canonicalize_path', return_value='/canon/path')
|
||||
@mock.patch.object(utils, 'backoff', return_value=[0])
|
||||
@mock.patch.object(subprocess, 'Popen', return_value=mock.Mock(**{
|
||||
'returncode': 0,
|
||||
'communicate.return_value': (None, None),
|
||||
}))
|
||||
def test_call_retry_success_badretries(self, mock_Popen, mock_backoff,
|
||||
mock_canonicalize_path, mock_chdir,
|
||||
mock_getcwd):
|
||||
logger = mock.Mock()
|
||||
env = environment.Environment(logger)
|
||||
retry = mock.Mock(return_value=True)
|
||||
|
||||
result = env(['test', 'one', 'two'], retry=retry, max_tries=-1)
|
||||
|
||||
self.assertEqual(result.cmd, ['test', 'one', 'two'])
|
||||
self.assertEqual(result.stdout, None)
|
||||
self.assertEqual(result.stderr, None)
|
||||
self.assertEqual(result.return_code, 0)
|
||||
self.assertFalse(mock_canonicalize_path.called)
|
||||
mock_backoff.assert_called_once_with(1)
|
||||
mock_Popen.assert_called_once_with(
|
||||
['test', 'one', 'two'], env=env, cwd='/some/path', close_fds=True)
|
||||
logger.assert_has_calls([
|
||||
mock.call.debug(
|
||||
"Executing command: ['test', 'one', 'two'] (cwd /some/path)"),
|
||||
])
|
||||
self.assertEqual(len(logger.method_calls), 1)
|
||||
self.assertFalse(retry.called)
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True, TEST_VAR1='1', TEST_VAR2='2')
|
||||
@mock.patch.object(os, 'getcwd', return_value='/some/path')
|
||||
@mock.patch.object(environment.Environment, 'chdir')
|
||||
@mock.patch.object(utils, 'canonicalize_path', return_value='/canon/path')
|
||||
@mock.patch.object(utils, 'backoff', return_value=[0, 1, 2, 3, 4, 5, 6])
|
||||
@mock.patch.object(subprocess, 'Popen', return_value=mock.Mock(**{
|
||||
'returncode': 0,
|
||||
'communicate.return_value': ('output', 'error'),
|
||||
}))
|
||||
def test_call_retry_withtries(self, mock_Popen, mock_backoff,
|
||||
mock_canonicalize_path, mock_chdir,
|
||||
mock_getcwd):
|
||||
logger = mock.Mock()
|
||||
env = environment.Environment(logger)
|
||||
retry = mock.Mock(return_value=True)
|
||||
exec_results = [
|
||||
mock.Mock(__nonzero__=mock.Mock(return_value=False),
|
||||
__bool__=mock.Mock(return_value=False)),
|
||||
mock.Mock(__nonzero__=mock.Mock(return_value=False),
|
||||
__bool__=mock.Mock(return_value=False)),
|
||||
mock.Mock(__nonzero__=mock.Mock(return_value=True),
|
||||
__bool__=mock.Mock(return_value=True)),
|
||||
]
|
||||
|
||||
with mock.patch.object(environment, 'ExecResult',
|
||||
side_effect=exec_results) as mock_ExecResult:
|
||||
result = env(['test', 'one', 'two'], retry=retry, max_tries=7)
|
||||
|
||||
self.assertEqual(result, exec_results[-1])
|
||||
self.assertFalse(mock_canonicalize_path.called)
|
||||
mock_backoff.assert_called_once_with(7)
|
||||
mock_Popen.assert_has_calls([
|
||||
mock.call(['test', 'one', 'two'], env=env, cwd='/some/path',
|
||||
close_fds=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE),
|
||||
mock.call(['test', 'one', 'two'], env=env, cwd='/some/path',
|
||||
close_fds=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE),
|
||||
mock.call(['test', 'one', 'two'], env=env, cwd='/some/path',
|
||||
close_fds=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE),
|
||||
])
|
||||
self.assertEqual(mock_Popen.call_count, 3)
|
||||
mock_ExecResult.assert_has_calls([
|
||||
mock.call(['test', 'one', 'two'], 'output', 'error', 0),
|
||||
mock.call(['test', 'one', 'two'], 'output', 'error', 0),
|
||||
mock.call(['test', 'one', 'two'], 'output', 'error', 0),
|
||||
])
|
||||
self.assertEqual(mock_ExecResult.call_count, 3)
|
||||
logger.assert_has_calls([
|
||||
mock.call.debug(
|
||||
"Executing command: ['test', 'one', 'two'] (cwd /some/path)"),
|
||||
mock.call.warn('Failure caught; retrying command (try #2)'),
|
||||
mock.call.warn('Failure caught; retrying command (try #3)'),
|
||||
])
|
||||
self.assertEqual(len(logger.method_calls), 3)
|
||||
retry.assert_has_calls([mock.call(res) for res in exec_results[:-1]])
|
||||
self.assertEqual(retry.call_count, len(exec_results) - 1)
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True, TEST_VAR1='1', TEST_VAR2='2')
|
||||
@mock.patch.object(os, 'getcwd', return_value='/some/path')
|
||||
@mock.patch.object(environment.Environment, 'chdir')
|
||||
@mock.patch.object(utils, 'canonicalize_path', return_value='/canon/path')
|
||||
@mock.patch.object(utils, 'backoff', return_value=[0, 1])
|
||||
@mock.patch.object(subprocess, 'Popen', return_value=mock.Mock(**{
|
||||
'returncode': 0,
|
||||
'communicate.return_value': ('output', 'error'),
|
||||
}))
|
||||
def test_call_retry_withtries_failure(self, mock_Popen, mock_backoff,
|
||||
mock_canonicalize_path, mock_chdir,
|
||||
mock_getcwd):
|
||||
logger = mock.Mock()
|
||||
env = environment.Environment(logger)
|
||||
retry = mock.Mock(return_value=True)
|
||||
exec_results = [
|
||||
mock.Mock(__nonzero__=mock.Mock(return_value=False),
|
||||
__bool__=mock.Mock(return_value=False)),
|
||||
mock.Mock(__nonzero__=mock.Mock(return_value=False),
|
||||
__bool__=mock.Mock(return_value=False)),
|
||||
mock.Mock(__nonzero__=mock.Mock(return_value=True),
|
||||
__bool__=mock.Mock(return_value=True)),
|
||||
]
|
||||
|
||||
with mock.patch.object(environment, 'ExecResult',
|
||||
side_effect=exec_results) as mock_ExecResult:
|
||||
result = env(['test', 'one', 'two'], retry=retry, max_tries=2,
|
||||
do_raise=False)
|
||||
|
||||
self.assertEqual(result, exec_results[-2])
|
||||
self.assertFalse(mock_canonicalize_path.called)
|
||||
mock_backoff.assert_called_once_with(2)
|
||||
mock_Popen.assert_has_calls([
|
||||
mock.call(['test', 'one', 'two'], env=env, cwd='/some/path',
|
||||
close_fds=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE),
|
||||
mock.call(['test', 'one', 'two'], env=env, cwd='/some/path',
|
||||
close_fds=True, stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE),
|
||||
])
|
||||
self.assertEqual(mock_Popen.call_count, 2)
|
||||
mock_ExecResult.assert_has_calls([
|
||||
mock.call(['test', 'one', 'two'], 'output', 'error', 0),
|
||||
mock.call(['test', 'one', 'two'], 'output', 'error', 0),
|
||||
])
|
||||
self.assertEqual(mock_ExecResult.call_count, 2)
|
||||
logger.assert_has_calls([
|
||||
mock.call.debug(
|
||||
"Executing command: ['test', 'one', 'two'] (cwd /some/path)"),
|
||||
mock.call.warn('Failure caught; retrying command (try #2)'),
|
||||
mock.call.warn('Unable to retry: too many attempts'),
|
||||
])
|
||||
self.assertEqual(len(logger.method_calls), 3)
|
||||
retry.assert_has_calls([mock.call(res) for res in exec_results[:-2]])
|
||||
self.assertEqual(retry.call_count, len(exec_results) - 1)
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True, TEST_VAR1='1', TEST_VAR2='2')
|
||||
@mock.patch.object(os, 'getcwd', return_value='/some/path')
|
||||
@mock.patch.object(utils, 'canonicalize_path', return_value='/canon/path')
|
||||
def test_chdir(self, mock_canonicalize_path, mock_getcwd):
|
||||
with mock.patch.object(environment.Environment, 'chdir'):
|
||||
env = environment.Environment('logger')
|
||||
|
||||
result = env.chdir('test/directory')
|
||||
|
||||
self.assertEqual(result, '/canon/path')
|
||||
self.assertEqual(env.cwd, '/canon/path')
|
||||
mock_canonicalize_path.assert_called_once_with(
|
||||
'/some/path', 'test/directory')
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True, TEST_VAR1='1', TEST_VAR2='2',
|
||||
PATH='/bin')
|
||||
@mock.patch.object(os, 'getcwd', return_value='/some/path')
|
||||
@mock.patch.object(os.path, 'exists', return_value=False)
|
||||
@mock.patch('shutil.rmtree')
|
||||
@mock.patch.object(environment.Environment, 'chdir')
|
||||
@mock.patch.object(environment.Environment, '__call__')
|
||||
@mock.patch.object(utils, 'canonicalize_path', return_value='/canon/path')
|
||||
def test_create_venv_basic(self, mock_canonicalize_path, mock_call,
|
||||
mock_chdir, mock_rmtree, mock_exists,
|
||||
mock_getcwd):
|
||||
logger = mock.Mock()
|
||||
env = environment.Environment(logger)
|
||||
expected = dict(env)
|
||||
expected.update({
|
||||
'VIRTUAL_ENV': '/canon/path',
|
||||
'PATH': '/canon/path/bin:/bin',
|
||||
})
|
||||
mock_chdir.reset_mock()
|
||||
|
||||
new_env = env.create_venv('venv/dir')
|
||||
|
||||
self.assertNotEqual(id(new_env), id(env))
|
||||
self.assertTrue(isinstance(new_env, environment.Environment))
|
||||
self.assertEqual(new_env, expected)
|
||||
self.assertEqual(new_env.logger, logger)
|
||||
self.assertEqual(new_env.cwd, '/some/path')
|
||||
self.assertEqual(new_env.venv_home, '/canon/path')
|
||||
mock_canonicalize_path.assert_called_once_with(
|
||||
'/some/path', 'venv/dir')
|
||||
mock_exists.assert_called_once_with('/canon/path')
|
||||
self.assertFalse(mock_rmtree.called)
|
||||
mock_call.assert_called_once_with(['virtualenv', '/canon/path'])
|
||||
mock_chdir.assert_called_once_with('/canon/path')
|
||||
logger.assert_has_calls([
|
||||
mock.call.debug('Preparing virtual environment /canon/path'),
|
||||
mock.call.info('Creating virtual environment /canon/path'),
|
||||
])
|
||||
self.assertEqual(len(logger.method_calls), 2)
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True, TEST_VAR1='1', TEST_VAR2='2',
|
||||
PATH='/bin')
|
||||
@mock.patch.object(os, 'getcwd', return_value='/some/path')
|
||||
@mock.patch.object(os.path, 'exists', return_value=False)
|
||||
@mock.patch('shutil.rmtree')
|
||||
@mock.patch.object(environment.Environment, 'chdir')
|
||||
@mock.patch.object(environment.Environment, '__call__')
|
||||
@mock.patch.object(utils, 'canonicalize_path', return_value='/canon/path')
|
||||
def test_create_venv_update(self, mock_canonicalize_path, mock_call,
|
||||
mock_chdir, mock_rmtree, mock_exists,
|
||||
mock_getcwd):
|
||||
logger = mock.Mock()
|
||||
env = environment.Environment(logger)
|
||||
expected = dict(env)
|
||||
expected.update({
|
||||
'VIRTUAL_ENV': 'bah',
|
||||
'PATH': '/canon/path/bin:/bin',
|
||||
'a': 'foo',
|
||||
})
|
||||
mock_chdir.reset_mock()
|
||||
|
||||
new_env = env.create_venv('venv/dir', VIRTUAL_ENV='bah', a='foo')
|
||||
|
||||
self.assertNotEqual(id(new_env), id(env))
|
||||
self.assertTrue(isinstance(new_env, environment.Environment))
|
||||
self.assertEqual(new_env, expected)
|
||||
self.assertEqual(new_env.logger, logger)
|
||||
self.assertEqual(new_env.cwd, '/some/path')
|
||||
self.assertEqual(new_env.venv_home, '/canon/path')
|
||||
mock_canonicalize_path.assert_called_once_with(
|
||||
'/some/path', 'venv/dir')
|
||||
mock_exists.assert_called_once_with('/canon/path')
|
||||
self.assertFalse(mock_rmtree.called)
|
||||
mock_call.assert_called_once_with(['virtualenv', '/canon/path'])
|
||||
mock_chdir.assert_called_once_with('/canon/path')
|
||||
logger.assert_has_calls([
|
||||
mock.call.debug('Preparing virtual environment /canon/path'),
|
||||
mock.call.info('Creating virtual environment /canon/path'),
|
||||
])
|
||||
self.assertEqual(len(logger.method_calls), 2)
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True, TEST_VAR1='1', TEST_VAR2='2',
|
||||
PATH='/bin')
|
||||
@mock.patch.object(os, 'getcwd', return_value='/some/path')
|
||||
@mock.patch.object(os.path, 'exists', return_value=True)
|
||||
@mock.patch('shutil.rmtree')
|
||||
@mock.patch.object(environment.Environment, 'chdir')
|
||||
@mock.patch.object(environment.Environment, '__call__')
|
||||
@mock.patch.object(utils, 'canonicalize_path', return_value='/canon/path')
|
||||
def test_create_venv_exists(self, mock_canonicalize_path, mock_call,
|
||||
mock_chdir, mock_rmtree, mock_exists,
|
||||
mock_getcwd):
|
||||
logger = mock.Mock()
|
||||
env = environment.Environment(logger)
|
||||
expected = dict(env)
|
||||
expected.update({
|
||||
'VIRTUAL_ENV': '/canon/path',
|
||||
'PATH': '/canon/path/bin:/bin',
|
||||
})
|
||||
mock_chdir.reset_mock()
|
||||
|
||||
new_env = env.create_venv('venv/dir')
|
||||
|
||||
self.assertNotEqual(id(new_env), id(env))
|
||||
self.assertTrue(isinstance(new_env, environment.Environment))
|
||||
self.assertEqual(new_env, expected)
|
||||
self.assertEqual(new_env.logger, logger)
|
||||
self.assertEqual(new_env.cwd, '/some/path')
|
||||
self.assertEqual(new_env.venv_home, '/canon/path')
|
||||
mock_canonicalize_path.assert_called_once_with(
|
||||
'/some/path', 'venv/dir')
|
||||
mock_exists.assert_called_once_with('/canon/path')
|
||||
self.assertFalse(mock_rmtree.called)
|
||||
self.assertFalse(mock_call.called)
|
||||
mock_chdir.assert_called_once_with('/canon/path')
|
||||
logger.assert_has_calls([
|
||||
mock.call.debug('Preparing virtual environment /canon/path'),
|
||||
mock.call.info('Using existing virtual environment /canon/path'),
|
||||
])
|
||||
self.assertEqual(len(logger.method_calls), 2)
|
||||
|
||||
@mock.patch.dict(os.environ, clear=True, TEST_VAR1='1', TEST_VAR2='2',
|
||||
PATH='/bin')
|
||||
@mock.patch.object(os, 'getcwd', return_value='/some/path')
|
||||
@mock.patch.object(os.path, 'exists', return_value=True)
|
||||
@mock.patch('shutil.rmtree')
|
||||
@mock.patch.object(environment.Environment, 'chdir')
|
||||
@mock.patch.object(environment.Environment, '__call__')
|
||||
@mock.patch.object(utils, 'canonicalize_path', return_value='/canon/path')
|
||||
def test_create_venv_rebuild(self, mock_canonicalize_path, mock_call,
|
||||
mock_chdir, mock_rmtree, mock_exists,
|
||||
mock_getcwd):
|
||||
logger = mock.Mock()
|
||||
env = environment.Environment(logger)
|
||||
expected = dict(env)
|
||||
expected.update({
|
||||
'VIRTUAL_ENV': '/canon/path',
|
||||
'PATH': '/canon/path/bin:/bin',
|
||||
})
|
||||
mock_chdir.reset_mock()
|
||||
|
||||
new_env = env.create_venv('venv/dir', True)
|
||||
|
||||
self.assertNotEqual(id(new_env), id(env))
|
||||
self.assertTrue(isinstance(new_env, environment.Environment))
|
||||
self.assertEqual(new_env, expected)
|
||||
self.assertEqual(new_env.logger, logger)
|
||||
self.assertEqual(new_env.cwd, '/some/path')
|
||||
self.assertEqual(new_env.venv_home, '/canon/path')
|
||||
mock_canonicalize_path.assert_called_once_with(
|
||||
'/some/path', 'venv/dir')
|
||||
mock_exists.assert_called_once_with('/canon/path')
|
||||
mock_rmtree.assert_called_once_with('/canon/path')
|
||||
mock_call.assert_called_once_with(['virtualenv', '/canon/path'])
|
||||
mock_chdir.assert_called_once_with('/canon/path')
|
||||
logger.assert_has_calls([
|
||||
mock.call.debug('Preparing virtual environment /canon/path'),
|
||||
mock.call.info('Destroying old virtual environment /canon/path'),
|
||||
mock.call.info('Creating virtual environment /canon/path'),
|
||||
])
|
||||
self.assertEqual(len(logger.method_calls), 3)
|
27
tox.ini
27
tox.ini
@ -1,27 +0,0 @@
|
||||
[tox]
|
||||
envlist = py26,py27,py33,py34,pep8
|
||||
|
||||
[testenv]
|
||||
setenv = LANG=en_US.UTF-8
|
||||
LANGUAGE=en_US:en
|
||||
LC_ALL=C
|
||||
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands = nosetests -v {posargs}
|
||||
|
||||
[testenv:pep8]
|
||||
deps = pep8
|
||||
commands = pep8 --repeat --show-source striker tests
|
||||
|
||||
[testenv:cover]
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands = nosetests -v --with-coverage --cover-package=striker \
|
||||
--cover-branches --cover-html --cover-html-dir=cov_html \
|
||||
{posargs}
|
||||
|
||||
[testenv:shell]
|
||||
deps = -r{toxinidir}/requirements.txt
|
||||
-r{toxinidir}/test-requirements.txt
|
||||
commands = {posargs}
|
Loading…
x
Reference in New Issue
Block a user