Using Make for a distribution
Archive - Originally posted on "The Horse's Mouth" - 2012-03-17 09:19:26 - Graham EllisAlthough Make has traditionally been used to build C and C++ programs, its uses are far wider - here's a new example in which I've used it to test a whole series of Python programs, and if they all test correctly to build a distribution (tar) file.
Scenario - three Python programs / classes for distribution. Each has some test code - first.py tests from first principles, second.py tests using doctest and third.py tests using unittest. Of course, I would normally be consistent in using one test harness or another, but this is a demonstration.
The makefile (full source [here]):
version=1.2
pyproject_$(version).tgz: first.tmp second.tmp third.tmp
tar czf pyproject_$(version).tgz project
@echo "BUILD COMPLETED"
# Simple Python Program
first.tmp: project/first.py
python project/first.py
@touch first.tmp
# Next example uses Python's doctest
second.tmp: project/second.py
python project/second.py
@touch second.tmp
# Next example uses Python's unittest
third.tmp: project/third.py
python project/third.py
@touch third.tmp
clean:
@rm -f *.tmp
@rm -f *.tgz
@echo "cleaned up"
Each of the Python test classes is written to return a successful status (0, because we're at shell level) if the test passes, and a failed status (1) if the test fails, so that the build will only complete through to distribution if all tests pass.
In first.py (full source [here]), the test I have added onto the end of the test harness is as follows:
if tpc != 495: exit(1)
exit(0)
where tpc is my test variable that should end up with a result of "495" if the code runs right.
Doctest return a status line that we can analyse similarly:
if status.find("failed=0") != -1: return 0
return 1
(my doctest code is written within a function, thus "return" rather than "exit").
See full source [here]
Unittest automatically exits with a success / failure status, so all I need to run my test is:
if __name__ == '__main__':
unittest.main()
See full source [here]
Let's run that - initially with a bug (for testing) in the doctest example:
wizard:pyproject graham$ make
python project/first.py
First Great Western cl 150 160 2
First Avon and Somerset 81 1
Faresaver 29 1
South West Trains cl 159 225 3
--- TOTAL --- 495 7
python project/second.py
**********************************************************************
File "project/second.py", line 4, in __main__.factorial
Failed example:
[factorial(n) for n in range(6)]
Expected:
[1, 1, 2, 7, 24, 120]
Got:
[1, 1, 2, 6, 24, 120]
**********************************************************************
1 items had failures:
1 of 1 in __main__.factorial
***Test Failed*** 1 failures.
make: *** [second.tmp] Error 1
wizard:pyproject graham$
Fixing the bug and trying again - you'll note the first test is NOT repeated:
wizard:pyproject graham$ make
python project/second.py
python project/third.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
tar czf pyproject_1.2.tgz project
BUILD COMPLETED
wizard:pyproject graham$
and running again:
wizard:pyproject graham$ make
make: `pyproject_1.2.tgz' is up to date.
wizard:pyproject graham$
I'm adding a section on make onto the end of next week's private Python course - the example above will be used during that course. Also happy to spend up to day going through Make on other private courses ...