├── .gitignore ├── .gitpod.Dockerfile ├── .gitpod.yml ├── .hgignore ├── .travis.yml ├── Contributor Notes.txt ├── MIT-LICENSE ├── README.rst ├── _runner_tests.py ├── contemplate_koans.py ├── example_file.txt ├── koans.txt ├── koans ├── GREEDS_RULES.txt ├── __init__.py ├── a_package_folder │ ├── __init__.py │ └── a_module.py ├── about_asserts.py ├── about_attribute_access.py ├── about_class_attributes.py ├── about_classes.py ├── about_comprehension.py ├── about_control_statements.py ├── about_decorating_with_classes.py ├── about_decorating_with_functions.py ├── about_deleting_objects.py ├── about_dice_project.py ├── about_dictionaries.py ├── about_exceptions.py ├── about_extra_credit.py ├── about_generators.py ├── about_inheritance.py ├── about_iteration.py ├── about_lambdas.py ├── about_list_assignments.py ├── about_lists.py ├── about_method_bindings.py ├── about_methods.py ├── about_modules.py ├── about_monkey_patching.py ├── about_multiple_inheritance.py ├── about_none.py ├── about_packages.py ├── about_proxy_object_project.py ├── about_regex.py ├── about_scope.py ├── about_scoring_project.py ├── about_sets.py ├── about_string_manipulation.py ├── about_strings.py ├── about_triangle_project.py ├── about_triangle_project2.py ├── about_true_and_false.py ├── about_tuples.py ├── about_with_statements.py ├── another_local_module.py ├── jims.py ├── joes.py ├── local_module.py ├── local_module_with_all_defined.py └── triangle.py ├── libs ├── __init__.py ├── colorama │ ├── LICENSE-colorama │ ├── __init__.py │ ├── ansi.py │ ├── ansitowin32.py │ ├── initialise.py │ ├── win32.py │ └── winterm.py └── mock.py ├── run.bat ├── run.sh ├── runner ├── __init__.py ├── helper.py ├── koan.py ├── mockable_test_result.py ├── mountain.py ├── path_to_enlightenment.py ├── runner_tests │ ├── __init__.py │ ├── test_helper.py │ ├── test_mountain.py │ ├── test_path_to_enlightenment.py │ └── test_sensei.py ├── sensei.py └── writeln_decorator.py └── scent.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.swp 3 | .DS_Store 4 | answers 5 | .hg 6 | .idea -------------------------------------------------------------------------------- /.gitpod.Dockerfile: -------------------------------------------------------------------------------- 1 | # Install custom tools, runtimes, etc. 2 | # For example "bastet", a command-line tetris clone: 3 | # RUN brew install bastet 4 | # 5 | # More information: https://www.gitpod.io/docs/config-docker/ 6 | 7 | FROM gitpod/workspace-full:latest 8 | 9 | USER gitpod 10 | 11 | RUN pip3 install pytest==4.4.2 pytest-testdox mock -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | image: 2 | file: .gitpod.Dockerfile 3 | 4 | tasks: 5 | - command: python contemplate_koans.py 6 | 7 | github: 8 | prebuilds: 9 | # enable for the master/default branch (defaults to true) 10 | master: true 11 | # enable for pull requests coming from this repo (defaults to true) 12 | pullRequests: false 13 | # add a "Review in Gitpod" button as a comment to pull requests (defaults to true) 14 | addComment: false 15 | -------------------------------------------------------------------------------- /.hgignore: -------------------------------------------------------------------------------- 1 | syntax: glob 2 | *.pyc 3 | *.swp 4 | .DS_Store 5 | answers 6 | .git 7 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | 3 | python: 4 | - 3.9 5 | 6 | script: 7 | - python _runner_tests.py 8 | # - python contemplate_koans.py # Run all the koans 9 | # - python contemplate_koans.py about_asserts about_none # Run a subset of 10 | # # koans 11 | # 12 | # Working through Python Koans in a fork? Want to use Travis CI to show which 13 | # koans you've passed? Then comment out one of the above "contemplate_koans" 14 | # lines above! 15 | # 16 | notifications: 17 | email: true 18 | 19 | # Some other koans (see runner/sensei.py or "ls koans" to see the light): 20 | # 21 | # about_none about_lists about_list_assignments about_dictionaries 22 | # about_strings about_tuples about_methods about_control_statements 23 | # about_true_and_false about_sets ... 24 | -------------------------------------------------------------------------------- /Contributor Notes.txt: -------------------------------------------------------------------------------- 1 | Testing a specific koan 2 | =================================== 3 | 4 | This will help when adding/modifying koans 5 | 6 | Running a whole test case: 7 | 8 | $ python3 contemplate_koans.py about_strings 9 | 10 | Running a single test: 11 | 12 | $ python3 contemplate_koans.py about_strings.AboutStrings.test_triple_quoted_strings_need_less_escaping 13 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2021 Greg Malcolm and The Status Is Not Quo 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Python Koans 3 | ============ 4 | 5 | .. image:: https://travis-ci.org/gregmalcolm/python_koans.png?branch=master 6 | :target: http://travis-ci.org/gregmalcolm/python_koans 7 | 8 | .. image:: https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod 9 | :target: https://gitpod.io/#https://github.com/gregmalcolm/python_koans 10 | 11 | .. image:: https://www.eclipse.org/che/contribute.svg 12 | :target: https://workspaces.openshift.com/f?url=https://gitpod.io/#https://github.com/gregmalcolm/python_koans 13 | 14 | One click installation: 15 | ----------------------- 16 | 17 | .. image:: https://www.eclipse.org/che/contribute.svg 18 | :target: https://workspaces.openshift.com/f?url=https://gitpod.io/#https://github.com/gregmalcolm/python_koans 19 | | or 20 | .. image:: https://gitpod.io/button/open-in-gitpod.svg 21 | :target: https://gitpod.io/#https://gitpod.io/#https://github.com/gregmalcolm/python_koans 22 | 23 | | 24 | 25 | Python Koans is a port of Edgecase's "Ruby Koans" which can be found 26 | at http://rubykoans.com/. 27 | 28 | .. image:: https://user-images.githubusercontent.com/2614930/28401740-ec6214b2-6cd0-11e7-8afd-30ed3102bfd6.png 29 | 30 | Python Koans is an interactive tutorial for learning the Python programming 31 | language by making tests pass. 32 | 33 | Most tests are *fixed* by filling the missing parts of assert functions. Eg: 34 | 35 | .. code-block:: python 36 | 37 | self.assertEqual(__, 1+2) 38 | 39 | which can be fixed by replacing the __ part with the appropriate code: 40 | 41 | .. code-block:: python 42 | 43 | self.assertEqual(3, 1+2) 44 | 45 | Occasionally you will encounter some failing tests that are already filled out. 46 | In these cases you will need to finish implementing some code to progress. For 47 | example, there is an exercise for writing some code that will tell you if a 48 | triangle is equilateral, isosceles or scalene. 49 | 50 | As well as being a great way to learn some Python, it is also a good way to get 51 | a taste of Test Driven Development (TDD). 52 | 53 | 54 | Downloading Python Koans 55 | ------------------------ 56 | 57 | Python Koans is available on GitHub: 58 | 59 | * https://github.com/gregmalcolm/python_koans 60 | 61 | You can clone with Git or download the source as a zip/gz/bz2. 62 | 63 | 64 | Installing Python Koans 65 | ----------------------- 66 | 67 | Aside from downloading or checking out the latest version of Python Koans, you 68 | need to install the Python interpreter. 69 | 70 | At this time of writing, we support Python 3. The policy is to try to keep 71 | current with the latest production version. 72 | 73 | You should be able to work with newer Python versions, but older ones will 74 | likely give you problems. 75 | 76 | You can download Python from here: 77 | 78 | * https://www.python.org/downloads/ 79 | 80 | After installing Python make sure the folder containing the python executable 81 | is in the system path. In other words, you need to be able to run Python from a 82 | command console. It will either be ``python3`` or for Windows it will be ``python.exe``. 83 | 84 | If you have problems, this may help: 85 | 86 | * https://www.python.org/about/gettingstarted/ 87 | 88 | Windows users may also want to update the line in the batch file ``run.bat`` to 89 | set the python path:: 90 | 91 | SET PYTHON_PATH=C:\Python39 92 | 93 | 94 | Getting Started 95 | --------------- 96 | 97 | Jake Hebbert has created a couple of screencasts available here: 98 | 99 | https://www.youtube.com/watch?v=e2WXgXEjbHY&list=PL5Up_u-XkWgNcunP_UrTJG_3EXgbK2BQJ&index=1 100 | 101 | Or if you prefer to read: 102 | 103 | From a \*nix terminal or Windows command prompt run:: 104 | 105 | .. code-block:: sh 106 | 107 | python contemplate_koans.py 108 | 109 | or: 110 | 111 | .. code-block:: sh 112 | 113 | python3 contemplate_koans.py 114 | 115 | In my case I'm using Python 3 with Windows, so I fire up my command 116 | shell (cmd.exe) and run this: 117 | 118 | .. image:: https://user-images.githubusercontent.com/2614930/28401747-f723ff00-6cd0-11e7-9b9a-a6993b753cf6.png 119 | 120 | Apparently a test failed:: 121 | 122 | AssertionError: False is not True 123 | 124 | It also tells me exactly where the problem is, it's an assert on line 12 125 | of ``.\\koans\\about_asserts.py``. This one is easy, just change ``False`` to ``True`` to 126 | make the test pass. 127 | 128 | Sooner or later you will likely encounter tests where you are not sure what the 129 | expected value should be. For example: 130 | 131 | .. code-block:: python 132 | 133 | class Dog: 134 | pass 135 | 136 | def test_objects_are_objects(self): 137 | fido = self.Dog() 138 | self.assertEqual(__, isinstance(fido, object)) 139 | 140 | This is where the Python Command Line can come in handy. In this case I can 141 | fire up the command line, recreate the scenario and run queries: 142 | 143 | .. image:: https://user-images.githubusercontent.com/2614930/28401750-f9dcb296-6cd0-11e7-98eb-c20318eada33.png 144 | 145 | Sniffer Support 146 | --------------- 147 | 148 | Sniffer allows you to run the tests continuously. If you modify any files files 149 | in the koans directory, it will rerun the tests. 150 | 151 | To set this up, you need to install sniffer: 152 | 153 | .. code-block:: sh 154 | 155 | python3 -m pip install sniffer 156 | 157 | You should also run one of these libraries depending on your system. This will 158 | automatically trigger sniffer when a file changes, otherwise sniffer will have 159 | to poll to see if the files have changed. 160 | 161 | On Linux: 162 | 163 | .. code-block:: sh 164 | 165 | python3 -m pip install pyinotify 166 | 167 | On Windows: 168 | 169 | .. code-block:: sh 170 | 171 | python3 -m pip install pywin32 172 | 173 | Also available here: 174 | 175 | https://github.com/mhammond/pywin32/releases 176 | 177 | On macOS: 178 | 179 | .. code-block:: sh 180 | 181 | python3 -m pip install MacFSEvents 182 | 183 | Once it is set up, you just run: 184 | 185 | .. code-block:: sh 186 | 187 | sniffer 188 | 189 | Just modify one of the koans files and you'll see that the tests are triggered 190 | automatically. Sniffer is controlled by ``scent.py``. 191 | 192 | Getting the Most From the Koans 193 | ------------------------------- 194 | 195 | Quoting the Ruby Koans instructions: 196 | 197 | "In test-driven development the mantra has always been, red, green, 198 | refactor. Write a failing test and run it (red), make the test pass 199 | (green), then refactor it (that is look at the code and see if you 200 | can make it any better). In this case you will need to run the koan 201 | and see it fail (red), make the test pass (green), then take a 202 | moment and reflect upon the test to see what it is teaching you 203 | and improve the code to better communicate its intent (refactor)." 204 | 205 | 206 | 207 | Finding More Koan Projects 208 | -------------------------- 209 | 210 | There are number of other great Koan projects out there for various languages 211 | and frameworks. Most of them can be found in GitHub. Also there is a little 212 | koans activity on Bitbucket. 213 | 214 | * GitHub koan projects: 215 | https://github.com/search?q=koans&ref=cmdform 216 | 217 | * Bitbucket koan projects: 218 | https://bitbucket.org/repo/all?name=koans 219 | 220 | Translations 221 | ------------ 222 | 223 | Translations are always welcome! Feel free to add one to this README 224 | if you happen to work on one: 225 | 226 | https://github.com/mswell/python_koans_br 227 | 228 | Acknowledgments 229 | --------------- 230 | 231 | Thanks go to Jim Weirich and Joe O'Brien for the original Ruby Koans that the 232 | Python Koans is based on! Also the Ruby Koans in turn borrows from Metakoans 233 | so thanks also go to Ara Howard for that! 234 | 235 | Also thanks to everyone who has contributed to Python Koans! I got a great 236 | headstart by taking over a code base initiated by the combined Mikes of 237 | FPIP. So here's a little plug for their very cool Python podcast: 238 | 239 | * https://www.frompythonimportpodcast.com/ 240 | 241 | A big thanks also to Mike Pirnat @pirnat and Kevin Chase @kjc have pitched in 242 | as co-maintainers at various times 243 | -------------------------------------------------------------------------------- /_runner_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | import unittest 6 | 7 | from runner.runner_tests.test_mountain import TestMountain 8 | from runner.runner_tests.test_sensei import TestSensei 9 | from runner.runner_tests.test_helper import TestHelper 10 | from runner.runner_tests.test_path_to_enlightenment import TestFilterKoanNames 11 | from runner.runner_tests.test_path_to_enlightenment import TestKoansSuite 12 | 13 | 14 | def suite(): 15 | suite = unittest.TestSuite() 16 | suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestMountain)) 17 | suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestSensei)) 18 | suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestHelper)) 19 | suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestFilterKoanNames)) 20 | suite.addTests(unittest.TestLoader().loadTestsFromTestCase(TestKoansSuite)) 21 | return suite 22 | 23 | 24 | if __name__ == '__main__': 25 | res = unittest.TextTestRunner(verbosity=2).run(suite()) 26 | sys.exit(not res.wasSuccessful()) 27 | -------------------------------------------------------------------------------- /contemplate_koans.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # 4 | # Acknowledgment: 5 | # 6 | # Python Koans is a port of Ruby Koans originally written by Jim Weirich 7 | # and Joe O'brien of Edgecase. There are some differences and tweaks specific 8 | # to the Python language, but a great deal of it has been copied wholesale. 9 | # So thanks guys! 10 | # 11 | 12 | import sys 13 | 14 | if __name__ == '__main__': 15 | if sys.version_info < (3, 0): 16 | print("\nThis is the Python 3 version of Python Koans, but you are " + 17 | "running it with Python 2!\n\n" 18 | "Did you accidentally use the wrong Python script? \nTry:\n\n" + 19 | " python3 contemplate_koans.py\n") 20 | else: 21 | if sys.version_info < (3, 7): 22 | print("\n" + 23 | "********************************************************\n" + 24 | "WARNING:\n" + 25 | "This version of Python Koans was designed for " + 26 | "Python 3.7 or greater.\n" + 27 | "Your version of Python is older, so you may run into " + 28 | "problems!\n\n" + 29 | "But let's see how far we get...\n" + 30 | "********************************************************\n") 31 | 32 | from runner.mountain import Mountain 33 | 34 | Mountain().walk_the_path(sys.argv) 35 | -------------------------------------------------------------------------------- /example_file.txt: -------------------------------------------------------------------------------- 1 | this 2 | is 3 | a 4 | test 5 | -------------------------------------------------------------------------------- /koans.txt: -------------------------------------------------------------------------------- 1 | # Lines starting with # are ignored. 2 | koans.about_asserts.AboutAsserts 3 | koans.about_strings.AboutStrings 4 | koans.about_none.AboutNone 5 | koans.about_lists.AboutLists 6 | koans.about_list_assignments.AboutListAssignments 7 | koans.about_dictionaries.AboutDictionaries 8 | koans.about_string_manipulation.AboutStringManipulation 9 | koans.about_tuples.AboutTuples 10 | koans.about_methods.AboutMethods 11 | koans.about_control_statements.AboutControlStatements 12 | koans.about_true_and_false.AboutTrueAndFalse 13 | koans.about_sets.AboutSets 14 | koans.about_triangle_project.AboutTriangleProject 15 | koans.about_exceptions.AboutExceptions 16 | koans.about_triangle_project2.AboutTriangleProject2 17 | koans.about_iteration.AboutIteration 18 | koans.about_comprehension.AboutComprehension 19 | koans.about_generators.AboutGenerators 20 | koans.about_lambdas.AboutLambdas 21 | koans.about_scoring_project.AboutScoringProject 22 | koans.about_classes.AboutClasses 23 | koans.about_with_statements.AboutWithStatements 24 | koans.about_monkey_patching.AboutMonkeyPatching 25 | koans.about_dice_project.AboutDiceProject 26 | koans.about_method_bindings.AboutMethodBindings 27 | koans.about_decorating_with_functions.AboutDecoratingWithFunctions 28 | koans.about_decorating_with_classes.AboutDecoratingWithClasses 29 | koans.about_inheritance.AboutInheritance 30 | koans.about_multiple_inheritance.AboutMultipleInheritance 31 | koans.about_scope.AboutScope 32 | koans.about_modules.AboutModules 33 | koans.about_packages.AboutPackages 34 | koans.about_class_attributes.AboutClassAttributes 35 | koans.about_attribute_access.AboutAttributeAccess 36 | koans.about_deleting_objects.AboutDeletingObjects 37 | koans.about_proxy_object_project.AboutProxyObjectProject 38 | koans.about_proxy_object_project.TelevisionTest 39 | koans.about_extra_credit.AboutExtraCredit 40 | koans.about_regex.AboutRegex 41 | -------------------------------------------------------------------------------- /koans/GREEDS_RULES.txt: -------------------------------------------------------------------------------- 1 | = Playing Greed 2 | 3 | Greed is a dice game played among 2 or more players, using 5 4 | six-sided dice. 5 | 6 | == Playing Greed 7 | 8 | Each player takes a turn consisting of one or more rolls of the dice. 9 | On the first roll of the game, a player rolls all five dice which are 10 | scored according to the following: 11 | 12 | Three 1's => 1000 points 13 | Three 6's => 600 points 14 | Three 5's => 500 points 15 | Three 4's => 400 points 16 | Three 3's => 300 points 17 | Three 2's => 200 points 18 | One 1 => 100 points 19 | One 5 => 50 points 20 | 21 | A single die can only be counted once in each roll. For example, 22 | a "5" can only count as part of a triplet (contributing to the 500 23 | points) or as a single 50 points, but not both in the same roll. 24 | 25 | Example Scoring 26 | 27 | Throw Score 28 | --------- ------------------ 29 | 5 1 3 4 1 50 + 2 * 100 = 250 30 | 1 1 1 3 1 1000 + 100 = 1100 31 | 2 4 4 5 4 400 + 50 = 450 32 | 33 | The dice not contributing to the score are called the non-scoring 34 | dice. "3" and "4" are non-scoring dice in the first example. "3" is 35 | a non-scoring die in the second, and "2" is a non-score die in the 36 | final example. 37 | 38 | After a player rolls and the score is calculated, the scoring dice are 39 | removed and the player has the option of rolling again using only the 40 | non-scoring dice. If all of the thrown dice are scoring, then the 41 | player may roll all 5 dice in the next roll. 42 | 43 | The player may continue to roll as long as each roll scores points. If 44 | a roll has zero points, then the player loses not only their turn, but 45 | also accumulated score for that turn. If a player decides to stop 46 | rolling before rolling a zero-point roll, then the accumulated points 47 | for the turn is added to his total score. 48 | 49 | == Getting "In The Game" 50 | 51 | Before a player is allowed to accumulate points, they must get at 52 | least 300 points in a single turn. Once they have achieved 300 points 53 | in a single turn, the points earned in that turn and each following 54 | turn will be counted toward their total score. 55 | 56 | == End Game 57 | 58 | Once a player reaches 3000 (or more) points, the game enters the final 59 | round where each of the other players gets one more turn. The winner 60 | is the player with the highest score after the final round. 61 | 62 | == References 63 | 64 | Greed is described on Wikipedia at 65 | http://en.wikipedia.org/wiki/Greed_(dice_game), however the rules are 66 | a bit different from the rules given here. 67 | -------------------------------------------------------------------------------- /koans/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # koans -------------------------------------------------------------------------------- /koans/a_package_folder/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | an_attribute = 1984 -------------------------------------------------------------------------------- /koans/a_package_folder/a_module.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | class Duck: 5 | @property 6 | def name(self): 7 | return "Donald" -------------------------------------------------------------------------------- /koans/about_asserts.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | class AboutAsserts(Koan): 7 | 8 | def test_assert_truth(self): 9 | """ 10 | We shall contemplate truth by testing reality, via asserts. 11 | """ 12 | 13 | # Confused? This video should help: 14 | # 15 | # http://bit.ly/about_asserts 16 | 17 | self.assertTrue(False) # This should be True 18 | 19 | def test_assert_with_message(self): 20 | """ 21 | Enlightenment may be more easily achieved with appropriate messages. 22 | """ 23 | self.assertTrue(False, "This should be True -- Please fix this") 24 | 25 | def test_fill_in_values(self): 26 | """ 27 | Sometimes we will ask you to fill in the values 28 | """ 29 | self.assertEqual(__, 1 + 1) 30 | 31 | def test_assert_equality(self): 32 | """ 33 | To understand reality, we must compare our expectations against reality. 34 | """ 35 | expected_value = __ 36 | actual_value = 1 + 1 37 | self.assertTrue(expected_value == actual_value) 38 | 39 | def test_a_better_way_of_asserting_equality(self): 40 | """ 41 | Some ways of asserting equality are better than others. 42 | """ 43 | expected_value = __ 44 | actual_value = 1 + 1 45 | 46 | self.assertEqual(expected_value, actual_value) 47 | 48 | def test_that_unittest_asserts_work_the_same_way_as_python_asserts(self): 49 | """ 50 | Understand what lies within. 51 | """ 52 | 53 | # This throws an AssertionError exception 54 | assert False 55 | 56 | def test_that_sometimes_we_need_to_know_the_class_type(self): 57 | """ 58 | What is in a class name? 59 | """ 60 | 61 | # Sometimes we will ask you what the class type of an object is. 62 | # 63 | # For example, contemplate the text string "navel". What is its class type? 64 | # The koans runner will include this feedback for this koan: 65 | # 66 | # AssertionError: '-=> FILL ME IN! <=-' != 67 | # 68 | # So "navel".__class__ is equal to ? No not quite. This 69 | # is just what it displays. The answer is simply str. 70 | # 71 | # See for yourself: 72 | 73 | self.assertEqual(__, "navel".__class__) # It's str, not 74 | 75 | # Need an illustration? More reading can be found here: 76 | # 77 | # https://github.com/gregmalcolm/python_koans/wiki/Class-Attribute 78 | 79 | -------------------------------------------------------------------------------- /koans/about_attribute_access.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 5 | # Partially based on AboutMessagePassing in the Ruby Koans 6 | # 7 | 8 | from runner.koan import * 9 | 10 | class AboutAttributeAccess(Koan): 11 | 12 | class TypicalObject: 13 | pass 14 | 15 | def test_calling_undefined_functions_normally_results_in_errors(self): 16 | typical = self.TypicalObject() 17 | 18 | with self.assertRaises(___): typical.foobar() 19 | 20 | def test_calling_getattribute_causes_an_attribute_error(self): 21 | typical = self.TypicalObject() 22 | 23 | with self.assertRaises(___): typical.__getattribute__('foobar') 24 | 25 | # THINK ABOUT IT: 26 | # 27 | # If the method __getattribute__() causes the AttributeError, then 28 | # what would happen if we redefine __getattribute__()? 29 | 30 | # ------------------------------------------------------------------ 31 | 32 | class CatchAllAttributeReads: 33 | def __getattribute__(self, attr_name): 34 | return "Someone called '" + attr_name + "' and it could not be found" 35 | 36 | def test_all_attribute_reads_are_caught(self): 37 | catcher = self.CatchAllAttributeReads() 38 | 39 | self.assertRegex(catcher.foobar, __) 40 | 41 | def test_intercepting_return_values_can_disrupt_the_call_chain(self): 42 | catcher = self.CatchAllAttributeReads() 43 | 44 | self.assertRegex(catcher.foobaz, __) # This is fine 45 | 46 | try: 47 | catcher.foobaz(1) 48 | except TypeError as ex: 49 | err_msg = ex.args[0] 50 | 51 | self.assertRegex(err_msg, __) 52 | 53 | # foobaz returns a string. What happens to the '(1)' part? 54 | # Try entering this into a python console to reproduce the issue: 55 | # 56 | # "foobaz"(1) 57 | # 58 | 59 | def test_changes_to_the_getattribute_implementation_affects_getattr_function(self): 60 | catcher = self.CatchAllAttributeReads() 61 | 62 | self.assertRegex(getattr(catcher, 'any_attribute'), __) 63 | 64 | # ------------------------------------------------------------------ 65 | 66 | class WellBehavedFooCatcher: 67 | def __getattribute__(self, attr_name): 68 | if attr_name[:3] == "foo": 69 | return "Foo to you too" 70 | else: 71 | return super().__getattribute__(attr_name) 72 | 73 | def test_foo_attributes_are_caught(self): 74 | catcher = self.WellBehavedFooCatcher() 75 | 76 | self.assertEqual(__, catcher.foo_bar) 77 | self.assertEqual(__, catcher.foo_baz) 78 | 79 | def test_non_foo_messages_are_treated_normally(self): 80 | catcher = self.WellBehavedFooCatcher() 81 | 82 | with self.assertRaises(___): catcher.normal_undefined_attribute 83 | 84 | # ------------------------------------------------------------------ 85 | 86 | global stack_depth 87 | stack_depth = 0 88 | 89 | class RecursiveCatcher: 90 | def __init__(self): 91 | global stack_depth 92 | stack_depth = 0 93 | self.no_of_getattribute_calls = 0 94 | 95 | def __getattribute__(self, attr_name): 96 | # We need something that is outside the scope of this class: 97 | global stack_depth 98 | stack_depth += 1 99 | 100 | if stack_depth<=10: # to prevent a stack overflow 101 | self.no_of_getattribute_calls += 1 102 | # Oops! We just accessed an attribute (no_of_getattribute_calls) 103 | # Guess what happens when self.no_of_getattribute_calls is 104 | # accessed? 105 | 106 | # Using 'object' directly because using super() here will also 107 | # trigger a __getattribute__() call. 108 | return object.__getattribute__(self, attr_name) 109 | 110 | def my_method(self): 111 | pass 112 | 113 | def test_getattribute_is_a_bit_overzealous_sometimes(self): 114 | catcher = self.RecursiveCatcher() 115 | catcher.my_method() 116 | global stack_depth 117 | self.assertEqual(__, stack_depth) 118 | 119 | # ------------------------------------------------------------------ 120 | 121 | class MinimalCatcher: 122 | class DuffObject: pass 123 | 124 | def __init__(self): 125 | self.no_of_getattr_calls = 0 126 | 127 | def __getattr__(self, attr_name): 128 | self.no_of_getattr_calls += 1 129 | return self.DuffObject 130 | 131 | def my_method(self): 132 | pass 133 | 134 | def test_getattr_ignores_known_attributes(self): 135 | catcher = self.MinimalCatcher() 136 | catcher.my_method() 137 | 138 | self.assertEqual(__, catcher.no_of_getattr_calls) 139 | 140 | def test_getattr_only_catches_unknown_attributes(self): 141 | catcher = self.MinimalCatcher() 142 | catcher.purple_flamingos() 143 | catcher.free_pie() 144 | 145 | self.assertEqual(__, 146 | type(catcher.give_me_duff_or_give_me_death()).__name__) 147 | 148 | self.assertEqual(__, catcher.no_of_getattr_calls) 149 | 150 | # ------------------------------------------------------------------ 151 | 152 | class PossessiveSetter(object): 153 | def __setattr__(self, attr_name, value): 154 | new_attr_name = attr_name 155 | 156 | if attr_name[-5:] == 'comic': 157 | new_attr_name = "my_" + new_attr_name 158 | elif attr_name[-3:] == 'pie': 159 | new_attr_name = "a_" + new_attr_name 160 | 161 | object.__setattr__(self, new_attr_name, value) 162 | 163 | def test_setattr_intercepts_attribute_assignments(self): 164 | fanboy = self.PossessiveSetter() 165 | 166 | fanboy.comic = 'The Laminator, issue #1' 167 | fanboy.pie = 'blueberry' 168 | 169 | self.assertEqual(__, fanboy.a_pie) 170 | 171 | # 172 | # NOTE: Change the prefix to make this next assert pass 173 | # 174 | 175 | prefix = '__' 176 | self.assertEqual("The Laminator, issue #1", getattr(fanboy, prefix + '_comic')) 177 | 178 | # ------------------------------------------------------------------ 179 | 180 | class ScarySetter: 181 | def __init__(self): 182 | self.num_of_coconuts = 9 183 | self._num_of_private_coconuts = 2 184 | 185 | def __setattr__(self, attr_name, value): 186 | new_attr_name = attr_name 187 | 188 | if attr_name[0] != '_': 189 | new_attr_name = "altered_" + new_attr_name 190 | 191 | object.__setattr__(self, new_attr_name, value) 192 | 193 | def test_it_modifies_external_attribute_as_expected(self): 194 | setter = self.ScarySetter() 195 | setter.e = "mc hammer" 196 | 197 | self.assertEqual(__, setter.altered_e) 198 | 199 | def test_it_mangles_some_internal_attributes(self): 200 | setter = self.ScarySetter() 201 | 202 | try: 203 | coconuts = setter.num_of_coconuts 204 | except AttributeError: 205 | self.assertEqual(__, setter.altered_num_of_coconuts) 206 | 207 | def test_in_this_case_private_attributes_remain_unmangled(self): 208 | setter = self.ScarySetter() 209 | 210 | self.assertEqual(__, setter._num_of_private_coconuts) 211 | -------------------------------------------------------------------------------- /koans/about_class_attributes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 5 | # Based on AboutClassMethods in the Ruby Koans 6 | # 7 | 8 | from runner.koan import * 9 | 10 | class AboutClassAttributes(Koan): 11 | class Dog: 12 | pass 13 | 14 | def test_objects_are_objects(self): 15 | fido = self.Dog() 16 | self.assertEqual(__, isinstance(fido, object)) 17 | 18 | def test_classes_are_types(self): 19 | self.assertEqual(__, self.Dog.__class__ == type) 20 | 21 | def test_classes_are_objects_too(self): 22 | self.assertEqual(__, issubclass(self.Dog, object)) 23 | 24 | def test_objects_have_methods(self): 25 | fido = self.Dog() 26 | self.assertEqual(__, len(dir(fido))) 27 | 28 | def test_classes_have_methods(self): 29 | self.assertEqual(__, len(dir(self.Dog))) 30 | 31 | def test_creating_objects_without_defining_a_class(self): 32 | singularity = object() 33 | self.assertEqual(__, len(dir(singularity))) 34 | 35 | def test_defining_attributes_on_individual_objects(self): 36 | fido = self.Dog() 37 | fido.legs = 4 38 | 39 | self.assertEqual(__, fido.legs) 40 | 41 | def test_defining_functions_on_individual_objects(self): 42 | fido = self.Dog() 43 | fido.wag = lambda : 'fidos wag' 44 | 45 | self.assertEqual(__, fido.wag()) 46 | 47 | def test_other_objects_are_not_affected_by_these_singleton_functions(self): 48 | fido = self.Dog() 49 | rover = self.Dog() 50 | 51 | def wag(): 52 | return 'fidos wag' 53 | fido.wag = wag 54 | 55 | with self.assertRaises(___): rover.wag() 56 | 57 | # ------------------------------------------------------------------ 58 | 59 | class Dog2: 60 | def wag(self): 61 | return 'instance wag' 62 | 63 | def bark(self): 64 | return "instance bark" 65 | 66 | def growl(self): 67 | return "instance growl" 68 | 69 | @staticmethod 70 | def bark(): 71 | return "staticmethod bark, arg: None" 72 | 73 | @classmethod 74 | def growl(cls): 75 | return "classmethod growl, arg: cls=" + cls.__name__ 76 | 77 | def test_since_classes_are_objects_you_can_define_singleton_methods_on_them_too(self): 78 | self.assertRegex(self.Dog2.growl(), __) 79 | 80 | def test_classmethods_are_not_independent_of_instance_methods(self): 81 | fido = self.Dog2() 82 | self.assertRegex(fido.growl(), __) 83 | self.assertRegex(self.Dog2.growl(), __) 84 | 85 | def test_staticmethods_are_unbound_functions_housed_in_a_class(self): 86 | self.assertRegex(self.Dog2.bark(), __) 87 | 88 | def test_staticmethods_also_overshadow_instance_methods(self): 89 | fido = self.Dog2() 90 | self.assertRegex(fido.bark(), __) 91 | 92 | # ------------------------------------------------------------------ 93 | 94 | class Dog3: 95 | def __init__(self): 96 | self._name = None 97 | 98 | def get_name_from_instance(self): 99 | return self._name 100 | 101 | def set_name_from_instance(self, name): 102 | self._name = name 103 | 104 | @classmethod 105 | def get_name(cls): 106 | return cls._name 107 | 108 | @classmethod 109 | def set_name(cls, name): 110 | cls._name = name 111 | 112 | name = property(get_name, set_name) 113 | name_from_instance = property(get_name_from_instance, set_name_from_instance) 114 | 115 | def test_classmethods_can_not_be_used_as_properties(self): 116 | fido = self.Dog3() 117 | with self.assertRaises(___): fido.name = "Fido" 118 | 119 | def test_classes_and_instances_do_not_share_instance_attributes(self): 120 | fido = self.Dog3() 121 | fido.set_name_from_instance("Fido") 122 | fido.set_name("Rover") 123 | self.assertEqual(__, fido.get_name_from_instance()) 124 | self.assertEqual(__, self.Dog3.get_name()) 125 | 126 | def test_classes_and_instances_do_share_class_attributes(self): 127 | fido = self.Dog3() 128 | fido.set_name("Fido") 129 | self.assertEqual(__, fido.get_name()) 130 | self.assertEqual(__, self.Dog3.get_name()) 131 | 132 | # ------------------------------------------------------------------ 133 | 134 | class Dog4: 135 | def a_class_method(cls): 136 | return 'dogs class method' 137 | 138 | def a_static_method(): 139 | return 'dogs static method' 140 | 141 | a_class_method = classmethod(a_class_method) 142 | a_static_method = staticmethod(a_static_method) 143 | 144 | def test_you_can_define_class_methods_without_using_a_decorator(self): 145 | self.assertEqual(__, self.Dog4.a_class_method()) 146 | 147 | def test_you_can_define_static_methods_without_using_a_decorator(self): 148 | self.assertEqual(__, self.Dog4.a_static_method()) 149 | 150 | # ------------------------------------------------------------------ 151 | 152 | def test_heres_an_easy_way_to_explicitly_call_class_methods_from_instance_methods(self): 153 | fido = self.Dog4() 154 | self.assertEqual(__, fido.__class__.a_class_method()) 155 | -------------------------------------------------------------------------------- /koans/about_classes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | 7 | class AboutClasses(Koan): 8 | class Dog: 9 | "Dogs need regular walkies. Never, ever let them drive." 10 | 11 | def test_instances_of_classes_can_be_created_adding_parentheses(self): 12 | # NOTE: The .__name__ attribute will convert the class 13 | # into a string value. 14 | fido = self.Dog() 15 | self.assertEqual(__, fido.__class__.__name__) 16 | 17 | def test_classes_have_docstrings(self): 18 | self.assertRegex(self.Dog.__doc__, __) 19 | 20 | # ------------------------------------------------------------------ 21 | 22 | class Dog2: 23 | def __init__(self): 24 | self._name = 'Paul' 25 | 26 | def set_name(self, a_name): 27 | self._name = a_name 28 | 29 | def test_init_method_is_the_constructor(self): 30 | dog = self.Dog2() 31 | self.assertEqual(__, dog._name) 32 | 33 | def test_private_attributes_are_not_really_private(self): 34 | dog = self.Dog2() 35 | dog.set_name("Fido") 36 | self.assertEqual(__, dog._name) 37 | # The _ prefix in _name implies private ownership, but nothing is truly 38 | # private in Python. 39 | 40 | def test_you_can_also_access_the_value_out_using_getattr_and_dict(self): 41 | fido = self.Dog2() 42 | fido.set_name("Fido") 43 | 44 | self.assertEqual(__, getattr(fido, "_name")) 45 | # getattr(), setattr() and delattr() are a way of accessing attributes 46 | # by method rather than through assignment operators 47 | 48 | self.assertEqual(__, fido.__dict__["_name"]) 49 | # Yes, this works here, but don't rely on the __dict__ object! Some 50 | # class implementations use optimization which result in __dict__ not 51 | # showing everything. 52 | 53 | # ------------------------------------------------------------------ 54 | 55 | class Dog3: 56 | def __init__(self): 57 | self._name = None 58 | 59 | def set_name(self, a_name): 60 | self._name = a_name 61 | 62 | def get_name(self): 63 | return self._name 64 | 65 | name = property(get_name, set_name) 66 | 67 | def test_that_name_can_be_read_as_a_property(self): 68 | fido = self.Dog3() 69 | fido.set_name("Fido") 70 | 71 | # access as method 72 | self.assertEqual(__, fido.get_name()) 73 | 74 | # access as property 75 | self.assertEqual(__, fido.name) 76 | 77 | # ------------------------------------------------------------------ 78 | 79 | class Dog4: 80 | def __init__(self): 81 | self._name = None 82 | 83 | @property 84 | def name(self): 85 | return self._name 86 | 87 | @name.setter 88 | def name(self, a_name): 89 | self._name = a_name 90 | 91 | def test_creating_properties_with_decorators_is_slightly_easier(self): 92 | fido = self.Dog4() 93 | 94 | fido.name = "Fido" 95 | self.assertEqual(__, fido.name) 96 | 97 | # ------------------------------------------------------------------ 98 | 99 | class Dog5: 100 | def __init__(self, initial_name): 101 | self._name = initial_name 102 | 103 | @property 104 | def name(self): 105 | return self._name 106 | 107 | def test_init_provides_initial_values_for_instance_variables(self): 108 | fido = self.Dog5("Fido") 109 | self.assertEqual(__, fido.name) 110 | 111 | def test_args_must_match_init(self): 112 | with self.assertRaises(___): 113 | self.Dog5() 114 | 115 | # THINK ABOUT IT: 116 | # Why is this so? 117 | 118 | def test_different_objects_have_different_instance_variables(self): 119 | fido = self.Dog5("Fido") 120 | rover = self.Dog5("Rover") 121 | 122 | self.assertEqual(__, rover.name == fido.name) 123 | 124 | # ------------------------------------------------------------------ 125 | 126 | class Dog6: 127 | def __init__(self, initial_name): 128 | self._name = initial_name 129 | 130 | def get_self(self): 131 | return self 132 | 133 | def __str__(self): 134 | # 135 | # Implement this! 136 | # 137 | return __ 138 | 139 | def __repr__(self): 140 | return "" 141 | 142 | def test_inside_a_method_self_refers_to_the_containing_object(self): 143 | fido = self.Dog6("Fido") 144 | 145 | self.assertEqual(__, fido.get_self()) # Not a string! 146 | 147 | def test_str_provides_a_string_version_of_the_object(self): 148 | fido = self.Dog6("Fido") 149 | 150 | self.assertEqual("Fido", str(fido)) 151 | 152 | def test_str_is_used_explicitly_in_string_interpolation(self): 153 | fido = self.Dog6("Fido") 154 | 155 | self.assertEqual(__, "My dog is " + str(fido)) 156 | 157 | def test_repr_provides_a_more_complete_string_version(self): 158 | fido = self.Dog6("Fido") 159 | self.assertEqual(__, repr(fido)) 160 | 161 | def test_all_objects_support_str_and_repr(self): 162 | seq = [1, 2, 3] 163 | 164 | self.assertEqual(__, str(seq)) 165 | self.assertEqual(__, repr(seq)) 166 | 167 | self.assertEqual(__, str("STRING")) 168 | self.assertEqual(__, repr("STRING")) 169 | -------------------------------------------------------------------------------- /koans/about_comprehension.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | 7 | class AboutComprehension(Koan): 8 | 9 | 10 | def test_creating_lists_with_list_comprehensions(self): 11 | feast = ['lambs', 'sloths', 'orangutans', 'breakfast cereals', 12 | 'fruit bats'] 13 | 14 | comprehension = [delicacy.capitalize() for delicacy in feast] 15 | 16 | self.assertEqual(__, comprehension[0]) 17 | self.assertEqual(__, comprehension[2]) 18 | 19 | def test_filtering_lists_with_list_comprehensions(self): 20 | feast = ['spam', 'sloths', 'orangutans', 'breakfast cereals', 21 | 'fruit bats'] 22 | 23 | comprehension = [delicacy for delicacy in feast if len(delicacy) > 6] 24 | 25 | self.assertEqual(__, len(feast)) 26 | self.assertEqual(__, len(comprehension)) 27 | 28 | def test_unpacking_tuples_in_list_comprehensions(self): 29 | list_of_tuples = [(1, 'lumberjack'), (2, 'inquisition'), (4, 'spam')] 30 | comprehension = [ skit * number for number, skit in list_of_tuples ] 31 | 32 | self.assertEqual(__, comprehension[0]) 33 | self.assertEqual(__, comprehension[2]) 34 | 35 | def test_double_list_comprehension(self): 36 | list_of_eggs = ['poached egg', 'fried egg'] 37 | list_of_meats = ['lite spam', 'ham spam', 'fried spam'] 38 | 39 | 40 | comprehension = [ '{0} and {1}'.format(egg, meat) for egg in list_of_eggs for meat in list_of_meats] 41 | 42 | 43 | self.assertEqual(__, comprehension[0]) 44 | self.assertEqual(__, len(comprehension)) 45 | 46 | def test_creating_a_set_with_set_comprehension(self): 47 | comprehension = { x for x in 'aabbbcccc'} 48 | 49 | self.assertEqual(__, comprehension) # remember that set members are unique 50 | 51 | def test_creating_a_dictionary_with_dictionary_comprehension(self): 52 | dict_of_weapons = {'first': 'fear', 'second': 'surprise', 53 | 'third':'ruthless efficiency', 'fourth':'fanatical devotion', 54 | 'fifth': None} 55 | 56 | dict_comprehension = { k.upper(): weapon for k, weapon in dict_of_weapons.items() if weapon} 57 | 58 | self.assertEqual(__, 'first' in dict_comprehension) 59 | self.assertEqual(__, 'FIRST' in dict_comprehension) 60 | self.assertEqual(__, len(dict_of_weapons)) 61 | self.assertEqual(__, len(dict_comprehension)) 62 | -------------------------------------------------------------------------------- /koans/about_control_statements.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | class AboutControlStatements(Koan): 7 | 8 | def test_if_then_else_statements(self): 9 | if True: 10 | result = 'true value' 11 | else: 12 | result = 'false value' 13 | self.assertEqual(__, result) 14 | 15 | def test_if_then_statements(self): 16 | result = 'default value' 17 | if True: 18 | result = 'true value' 19 | self.assertEqual(__, result) 20 | 21 | def test_if_then_elif_else_statements(self): 22 | if False: 23 | result = 'first value' 24 | elif True: 25 | result = 'true value' 26 | else: 27 | result = 'default value' 28 | self.assertEqual(__, result) 29 | 30 | def test_while_statement(self): 31 | i = 1 32 | result = 1 33 | while i <= 10: 34 | result = result * i 35 | i += 1 36 | self.assertEqual(__, result) 37 | 38 | def test_break_statement(self): 39 | i = 1 40 | result = 1 41 | while True: 42 | if i > 10: break 43 | result = result * i 44 | i += 1 45 | self.assertEqual(__, result) 46 | 47 | def test_continue_statement(self): 48 | i = 0 49 | result = [] 50 | while i < 10: 51 | i += 1 52 | if (i % 2) == 0: continue 53 | result.append(i) 54 | self.assertEqual(__, result) 55 | 56 | def test_for_statement(self): 57 | phrase = ["fish", "and", "chips"] 58 | result = [] 59 | for item in phrase: 60 | result.append(item.upper()) 61 | self.assertEqual([__, __, __], result) 62 | 63 | def test_for_statement_with_tuples(self): 64 | round_table = [ 65 | ("Lancelot", "Blue"), 66 | ("Galahad", "I don't know!"), 67 | ("Robin", "Blue! I mean Green!"), 68 | ("Arthur", "Is that an African Swallow or European Swallow?") 69 | ] 70 | result = [] 71 | for knight, answer in round_table: 72 | result.append("Contestant: '" + knight + "' Answer: '" + answer + "'") 73 | 74 | text = __ 75 | 76 | self.assertRegex(result[2], text) 77 | 78 | self.assertNotRegex(result[0], text) 79 | self.assertNotRegex(result[1], text) 80 | self.assertNotRegex(result[3], text) 81 | -------------------------------------------------------------------------------- /koans/about_decorating_with_classes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | import functools 7 | 8 | class AboutDecoratingWithClasses(Koan): 9 | def maximum(self, a, b): 10 | if a>b: 11 | return a 12 | else: 13 | return b 14 | 15 | def test_partial_that_wrappers_no_args(self): 16 | """ 17 | Before we can understand this type of decorator we need to consider 18 | the partial. 19 | """ 20 | max = functools.partial(self.maximum) 21 | 22 | self.assertEqual(__, max(7,23)) 23 | self.assertEqual(__, max(10,-10)) 24 | 25 | def test_partial_that_wrappers_first_arg(self): 26 | max0 = functools.partial(self.maximum, 0) 27 | 28 | self.assertEqual(__, max0(-4)) 29 | self.assertEqual(__, max0(5)) 30 | 31 | def test_partial_that_wrappers_all_args(self): 32 | always99 = functools.partial(self.maximum, 99, 20) 33 | always20 = functools.partial(self.maximum, 9, 20) 34 | 35 | self.assertEqual(__, always99()) 36 | self.assertEqual(__, always20()) 37 | 38 | # ------------------------------------------------------------------ 39 | 40 | class doubleit: 41 | def __init__(self, fn): 42 | self.fn = fn 43 | 44 | def __call__(self, *args): 45 | return self.fn(*args) + ', ' + self.fn(*args) 46 | 47 | def __get__(self, obj, cls=None): 48 | if not obj: 49 | # Decorating an unbound function 50 | return self 51 | else: 52 | # Decorating a bound method 53 | return functools.partial(self, obj) 54 | 55 | @doubleit 56 | def foo(self): 57 | return "foo" 58 | 59 | @doubleit 60 | def parrot(self, text): 61 | return text.upper() 62 | 63 | def test_decorator_with_no_arguments(self): 64 | # To clarify: the decorator above the function has no arguments, even 65 | # if the decorated function does 66 | 67 | self.assertEqual(__, self.foo()) 68 | self.assertEqual(__, self.parrot('pieces of eight')) 69 | 70 | # ------------------------------------------------------------------ 71 | 72 | def sound_check(self): 73 | #Note: no decorator 74 | return "Testing..." 75 | 76 | def test_what_a_decorator_is_doing_to_a_function(self): 77 | #wrap the function with the decorator 78 | self.sound_check = self.doubleit(self.sound_check) 79 | 80 | self.assertEqual(__, self.sound_check()) 81 | 82 | # ------------------------------------------------------------------ 83 | 84 | class documenter: 85 | def __init__(self, *args): 86 | self.fn_doc = args[0] 87 | 88 | def __call__(self, fn): 89 | def decorated_function(*args): 90 | return fn(*args) 91 | 92 | if fn.__doc__: 93 | decorated_function.__doc__ = fn.__doc__ + ": " + self.fn_doc 94 | else: 95 | decorated_function.__doc__ = self.fn_doc 96 | return decorated_function 97 | 98 | @documenter("Increments a value by one. Kind of.") 99 | def count_badly(self, num): 100 | num += 1 101 | if num==3: 102 | return 5 103 | else: 104 | return num 105 | @documenter("Does nothing") 106 | def idler(self, num): 107 | "Idler" 108 | pass 109 | 110 | def test_decorator_with_an_argument(self): 111 | self.assertEqual(__, self.count_badly(2)) 112 | self.assertEqual(__, self.count_badly.__doc__) 113 | 114 | def test_documentor_which_already_has_a_docstring(self): 115 | self.assertEqual(__, self.idler.__doc__) 116 | 117 | # ------------------------------------------------------------------ 118 | 119 | @documenter("DOH!") 120 | @doubleit 121 | @doubleit 122 | def homer(self): 123 | return "D'oh" 124 | 125 | def test_we_can_chain_decorators(self): 126 | self.assertEqual(__, self.homer()) 127 | self.assertEqual(__, self.homer.__doc__) 128 | 129 | -------------------------------------------------------------------------------- /koans/about_decorating_with_functions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | 7 | class AboutDecoratingWithFunctions(Koan): 8 | def addcowbell(fn): 9 | fn.wow_factor = 'COWBELL BABY!' 10 | return fn 11 | 12 | @addcowbell 13 | def mediocre_song(self): 14 | return "o/~ We all live in a broken submarine o/~" 15 | 16 | def test_decorators_can_modify_a_function(self): 17 | self.assertRegex(self.mediocre_song(), __) 18 | self.assertEqual(__, self.mediocre_song.wow_factor) 19 | 20 | # ------------------------------------------------------------------ 21 | 22 | def xmltag(fn): 23 | def func(*args): 24 | return '<' + fn(*args) + '/>' 25 | return func 26 | 27 | @xmltag 28 | def render_tag(self, name): 29 | return name 30 | 31 | def test_decorators_can_change_a_function_output(self): 32 | self.assertEqual(__, self.render_tag('llama')) 33 | -------------------------------------------------------------------------------- /koans/about_deleting_objects.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | class AboutDeletingObjects(Koan): 7 | def test_del_can_remove_slices(self): 8 | lottery_nums = [4, 8, 15, 16, 23, 42] 9 | del lottery_nums[1] 10 | del lottery_nums[2:4] 11 | 12 | self.assertEqual(___, lottery_nums) 13 | 14 | def test_del_can_remove_entire_lists(self): 15 | lottery_nums = [4, 8, 15, 16, 23, 42] 16 | del lottery_nums 17 | 18 | with self.assertRaises(___): win = lottery_nums 19 | 20 | # ==================================================================== 21 | 22 | class ClosingSale: 23 | def __init__(self): 24 | self.hamsters = 7 25 | self.zebras = 84 26 | 27 | def cameras(self): 28 | return 34 29 | 30 | def toilet_brushes(self): 31 | return 48 32 | 33 | def jellies(self): 34 | return 5 35 | 36 | def test_del_can_remove_attributes(self): 37 | crazy_discounts = self.ClosingSale() 38 | del self.ClosingSale.toilet_brushes 39 | del crazy_discounts.hamsters 40 | 41 | try: 42 | still_available = crazy_discounts.toilet_brushes() 43 | except AttributeError as e: 44 | err_msg1 = e.args[0] 45 | 46 | try: 47 | still_available = crazy_discounts.hamsters 48 | except AttributeError as e: 49 | err_msg2 = e.args[0] 50 | 51 | self.assertRegex(err_msg1, __) 52 | self.assertRegex(err_msg2, __) 53 | 54 | # ==================================================================== 55 | 56 | class ClintEastwood: 57 | def __init__(self): 58 | self._name = None 59 | 60 | def get_name(self): 61 | try: 62 | return self._name 63 | except: 64 | return "The man with no name" 65 | 66 | def set_name(self, name): 67 | self._name = name 68 | 69 | def del_name(self): 70 | del self._name 71 | 72 | name = property(get_name, set_name, del_name, \ 73 | "Mr Eastwood's current alias") 74 | 75 | def test_del_works_with_properties(self): 76 | cowboy = self.ClintEastwood() 77 | cowboy.name = 'Senor Ninguno' 78 | self.assertEqual('Senor Ninguno', cowboy.name) 79 | 80 | del cowboy.name 81 | self.assertEqual(__, cowboy.name) 82 | 83 | 84 | # ==================================================================== 85 | 86 | class Prisoner: 87 | def __init__(self): 88 | self._name = None 89 | 90 | @property 91 | def name(self): 92 | return self._name 93 | 94 | @name.setter 95 | def name(self, name): 96 | self._name = name 97 | 98 | @name.deleter 99 | def name(self): 100 | self._name = 'Number Six' 101 | 102 | def test_another_way_to_make_a_deletable_property(self): 103 | citizen = self.Prisoner() 104 | citizen.name = "Patrick" 105 | self.assertEqual('Patrick', citizen.name) 106 | 107 | del citizen.name 108 | self.assertEqual(__, citizen.name) 109 | 110 | # ==================================================================== 111 | 112 | class MoreOrganisedClosingSale(ClosingSale): 113 | def __init__(self): 114 | self.last_deletion = None 115 | super().__init__() 116 | 117 | def __delattr__(self, attr_name): 118 | self.last_deletion = attr_name 119 | 120 | def tests_del_can_be_overriden(self): 121 | sale = self.MoreOrganisedClosingSale() 122 | self.assertEqual(__, sale.jellies()) 123 | del sale.jellies 124 | self.assertEqual(__, sale.last_deletion) 125 | -------------------------------------------------------------------------------- /koans/about_dice_project.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | import random 7 | 8 | class DiceSet: 9 | def __init__(self): 10 | self._values = None 11 | 12 | @property 13 | def values(self): 14 | return self._values 15 | 16 | def roll(self, n): 17 | # Needs implementing! 18 | # Tip: random.randint(min, max) can be used to generate random numbers 19 | pass 20 | 21 | class AboutDiceProject(Koan): 22 | def test_can_create_a_dice_set(self): 23 | dice = DiceSet() 24 | self.assertTrue(dice) 25 | 26 | def test_rolling_the_dice_returns_a_set_of_integers_between_1_and_6(self): 27 | dice = DiceSet() 28 | 29 | dice.roll(5) 30 | self.assertTrue(isinstance(dice.values, list), "should be a list") 31 | self.assertEqual(5, len(dice.values)) 32 | for value in dice.values: 33 | self.assertTrue(value >= 1 and value <= 6, "value " + str(value) + " must be between 1 and 6") 34 | 35 | def test_dice_values_do_not_change_unless_explicitly_rolled(self): 36 | dice = DiceSet() 37 | dice.roll(5) 38 | first_time = dice.values 39 | second_time = dice.values 40 | self.assertEqual(first_time, second_time) 41 | 42 | def test_dice_values_should_change_between_rolls(self): 43 | dice = DiceSet() 44 | 45 | dice.roll(5) 46 | first_time = dice.values 47 | 48 | dice.roll(5) 49 | second_time = dice.values 50 | 51 | self.assertNotEqual(first_time, second_time, \ 52 | "Two rolls should not be equal") 53 | 54 | # THINK ABOUT IT: 55 | # 56 | # If the rolls are random, then it is possible (although not 57 | # likely) that two consecutive rolls are equal. What would be a 58 | # better way to test this? 59 | 60 | def test_you_can_roll_different_numbers_of_dice(self): 61 | dice = DiceSet() 62 | 63 | dice.roll(3) 64 | self.assertEqual(3, len(dice.values)) 65 | 66 | dice.roll(1) 67 | self.assertEqual(1, len(dice.values)) 68 | -------------------------------------------------------------------------------- /koans/about_dictionaries.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 5 | # Based on AboutHashes in the Ruby Koans 6 | # 7 | 8 | from runner.koan import * 9 | 10 | class AboutDictionaries(Koan): 11 | def test_creating_dictionaries(self): 12 | empty_dict = dict() 13 | self.assertEqual(dict, type(empty_dict)) 14 | self.assertDictEqual({}, empty_dict) 15 | self.assertEqual(__, len(empty_dict)) 16 | 17 | def test_dictionary_literals(self): 18 | empty_dict = {} 19 | self.assertEqual(dict, type(empty_dict)) 20 | babel_fish = { 'one': 'uno', 'two': 'dos' } 21 | self.assertEqual(__, len(babel_fish)) 22 | 23 | def test_accessing_dictionaries(self): 24 | babel_fish = { 'one': 'uno', 'two': 'dos' } 25 | self.assertEqual(__, babel_fish['one']) 26 | self.assertEqual(__, babel_fish['two']) 27 | 28 | def test_changing_dictionaries(self): 29 | babel_fish = { 'one': 'uno', 'two': 'dos' } 30 | babel_fish['one'] = 'eins' 31 | 32 | expected = { 'two': 'dos', 'one': __ } 33 | self.assertDictEqual(expected, babel_fish) 34 | 35 | def test_dictionary_is_unordered(self): 36 | dict1 = { 'one': 'uno', 'two': 'dos' } 37 | dict2 = { 'two': 'dos', 'one': 'uno' } 38 | 39 | self.assertEqual(__, dict1 == dict2) 40 | 41 | 42 | def test_dictionary_keys_and_values(self): 43 | babel_fish = {'one': 'uno', 'two': 'dos'} 44 | self.assertEqual(__, len(babel_fish.keys())) 45 | self.assertEqual(__, len(babel_fish.values())) 46 | self.assertEqual(__, 'one' in babel_fish.keys()) 47 | self.assertEqual(__, 'two' in babel_fish.values()) 48 | self.assertEqual(__, 'uno' in babel_fish.keys()) 49 | self.assertEqual(__, 'dos' in babel_fish.values()) 50 | 51 | def test_making_a_dictionary_from_a_sequence_of_keys(self): 52 | cards = {}.fromkeys(('red warrior', 'green elf', 'blue valkyrie', 'yellow dwarf', 'confused looking zebra'), 42) 53 | 54 | self.assertEqual(__, len(cards)) 55 | self.assertEqual(__, cards['green elf']) 56 | self.assertEqual(__, cards['yellow dwarf']) 57 | 58 | -------------------------------------------------------------------------------- /koans/about_exceptions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | class AboutExceptions(Koan): 7 | 8 | class MySpecialError(RuntimeError): 9 | pass 10 | 11 | def test_exceptions_inherit_from_exception(self): 12 | mro = self.MySpecialError.mro() 13 | self.assertEqual(__, mro[1].__name__) 14 | self.assertEqual(__, mro[2].__name__) 15 | self.assertEqual(__, mro[3].__name__) 16 | self.assertEqual(__, mro[4].__name__) 17 | 18 | def test_try_clause(self): 19 | result = None 20 | try: 21 | self.fail("Oops") 22 | except Exception as ex: 23 | result = 'exception handled' 24 | 25 | ex2 = ex 26 | 27 | self.assertEqual(__, result) 28 | 29 | self.assertEqual(__, isinstance(ex2, Exception)) 30 | self.assertEqual(__, isinstance(ex2, RuntimeError)) 31 | 32 | self.assertTrue(issubclass(RuntimeError, Exception), \ 33 | "RuntimeError is a subclass of Exception") 34 | 35 | self.assertEqual(__, ex2.args[0]) 36 | 37 | def test_raising_a_specific_error(self): 38 | result = None 39 | try: 40 | raise self.MySpecialError("My Message") 41 | except self.MySpecialError as ex: 42 | result = 'exception handled' 43 | msg = ex.args[0] 44 | 45 | self.assertEqual(__, result) 46 | self.assertEqual(__, msg) 47 | 48 | def test_else_clause(self): 49 | result = None 50 | try: 51 | pass 52 | except RuntimeError: 53 | result = 'it broke' 54 | pass 55 | else: 56 | result = 'no damage done' 57 | 58 | self.assertEqual(__, result) 59 | 60 | 61 | def test_finally_clause(self): 62 | result = None 63 | try: 64 | self.fail("Oops") 65 | except: 66 | # no code here 67 | pass 68 | finally: 69 | result = 'always run' 70 | 71 | self.assertEqual(__, result) 72 | -------------------------------------------------------------------------------- /koans/about_extra_credit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # EXTRA CREDIT: 5 | # 6 | # Create a program that will play the Greed Game. 7 | # Rules for the game are in GREED_RULES.TXT. 8 | # 9 | # You already have a DiceSet class and score function you can use. 10 | # Write a player class and a Game class to complete the project. This 11 | # is a free form assignment, so approach it however you desire. 12 | 13 | from runner.koan import * 14 | 15 | class AboutExtraCredit(Koan): 16 | # Write tests here. If you need extra test classes add them to the 17 | # test suite in runner/path_to_enlightenment.py 18 | def test_extra_credit_task(self): 19 | pass 20 | -------------------------------------------------------------------------------- /koans/about_generators.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 5 | # Written in place of AboutBlocks in the Ruby Koans 6 | # 7 | # Note: Both blocks and generators use a yield keyword, but they behave 8 | # a lot differently 9 | # 10 | 11 | from runner.koan import * 12 | 13 | class AboutGenerators(Koan): 14 | 15 | def test_generating_values_on_the_fly(self): 16 | result = list() 17 | bacon_generator = (n + ' bacon' for n in ['crunchy','veggie','danish']) 18 | 19 | for bacon in bacon_generator: 20 | result.append(bacon) 21 | 22 | self.assertEqual(__, result) 23 | 24 | def test_generators_are_different_to_list_comprehensions(self): 25 | num_list = [x*2 for x in range(1,3)] 26 | num_generator = (x*2 for x in range(1,3)) 27 | 28 | self.assertEqual(2, num_list[0]) 29 | 30 | # A generator has to be iterated through. 31 | with self.assertRaises(___): num = num_generator[0] 32 | 33 | self.assertEqual(__, list(num_generator)[0]) 34 | 35 | # Both list comprehensions and generators can be iterated though. However, a generator 36 | # function is only called on the first iteration. The values are generated on the fly 37 | # instead of stored. 38 | # 39 | # Generators are more memory friendly, but less versatile 40 | 41 | def test_generator_expressions_are_a_one_shot_deal(self): 42 | dynamite = ('Boom!' for n in range(3)) 43 | 44 | attempt1 = list(dynamite) 45 | attempt2 = list(dynamite) 46 | 47 | self.assertEqual(__, attempt1) 48 | self.assertEqual(__, attempt2) 49 | 50 | # ------------------------------------------------------------------ 51 | 52 | def simple_generator_method(self): 53 | yield 'peanut' 54 | yield 'butter' 55 | yield 'and' 56 | yield 'jelly' 57 | 58 | def test_generator_method_will_yield_values_during_iteration(self): 59 | result = list() 60 | for item in self.simple_generator_method(): 61 | result.append(item) 62 | self.assertEqual(__, result) 63 | 64 | def test_generators_can_be_manually_iterated_and_closed(self): 65 | result = self.simple_generator_method() 66 | self.assertEqual(__, next(result)) 67 | self.assertEqual(__, next(result)) 68 | result.close() 69 | 70 | # ------------------------------------------------------------------ 71 | 72 | def square_me(self, seq): 73 | for x in seq: 74 | yield x * x 75 | 76 | def test_generator_method_with_parameter(self): 77 | result = self.square_me(range(2,5)) 78 | self.assertEqual(__, list(result)) 79 | 80 | # ------------------------------------------------------------------ 81 | 82 | def sum_it(self, seq): 83 | value = 0 84 | for num in seq: 85 | # The local state of 'value' will be retained between iterations 86 | value += num 87 | yield value 88 | 89 | def test_generator_keeps_track_of_local_variables(self): 90 | result = self.sum_it(range(2,5)) 91 | self.assertEqual(__, list(result)) 92 | 93 | # ------------------------------------------------------------------ 94 | 95 | def coroutine(self): 96 | result = yield 97 | yield result 98 | 99 | def test_generators_can_act_as_coroutines(self): 100 | generator = self.coroutine() 101 | 102 | # THINK ABOUT IT: 103 | # Why is this line necessary? 104 | # 105 | # Hint: Read the "Specification: Sending Values into Generators" 106 | # section of http://www.python.org/dev/peps/pep-0342/ 107 | next(generator) 108 | 109 | self.assertEqual(__, generator.send(1 + 2)) 110 | 111 | def test_before_sending_a_value_to_a_generator_next_must_be_called(self): 112 | generator = self.coroutine() 113 | 114 | try: 115 | generator.send(1 + 2) 116 | except TypeError as ex: 117 | self.assertRegex(ex.args[0], __) 118 | 119 | # ------------------------------------------------------------------ 120 | 121 | def yield_tester(self): 122 | value = yield 123 | if value: 124 | yield value 125 | else: 126 | yield 'no value' 127 | 128 | def test_generators_can_see_if_they_have_been_called_with_a_value(self): 129 | generator = self.yield_tester() 130 | next(generator) 131 | self.assertEqual('with value', generator.send('with value')) 132 | 133 | generator2 = self.yield_tester() 134 | next(generator2) 135 | self.assertEqual(__, next(generator2)) 136 | 137 | def test_send_none_is_equivalent_to_next(self): 138 | generator = self.yield_tester() 139 | 140 | next(generator) 141 | # 'next(generator)' is exactly equivalent to 'generator.send(None)' 142 | self.assertEqual(__, generator.send(None)) 143 | -------------------------------------------------------------------------------- /koans/about_inheritance.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | class AboutInheritance(Koan): 7 | class Dog: 8 | def __init__(self, name): 9 | self._name = name 10 | 11 | @property 12 | def name(self): 13 | return self._name 14 | 15 | def bark(self): 16 | return "WOOF" 17 | 18 | class Chihuahua(Dog): 19 | def wag(self): 20 | return "happy" 21 | 22 | def bark(self): 23 | return "yip" 24 | 25 | def test_subclasses_have_the_parent_as_an_ancestor(self): 26 | self.assertEqual(__, issubclass(self.Chihuahua, self.Dog)) 27 | 28 | def test_all_classes_in_python_3_ultimately_inherit_from_object_class(self): 29 | self.assertEqual(__, issubclass(self.Chihuahua, object)) 30 | 31 | # Note: This isn't the case in Python 2. In that version you have 32 | # to inherit from a built in class or object explicitly 33 | 34 | def test_instances_inherit_behavior_from_parent_class(self): 35 | chico = self.Chihuahua("Chico") 36 | self.assertEqual(__, chico.name) 37 | 38 | def test_subclasses_add_new_behavior(self): 39 | chico = self.Chihuahua("Chico") 40 | self.assertEqual(__, chico.wag()) 41 | 42 | fido = self.Dog("Fido") 43 | with self.assertRaises(___): fido.wag() 44 | 45 | def test_subclasses_can_modify_existing_behavior(self): 46 | chico = self.Chihuahua("Chico") 47 | self.assertEqual(__, chico.bark()) 48 | 49 | fido = self.Dog("Fido") 50 | self.assertEqual(__, fido.bark()) 51 | 52 | # ------------------------------------------------------------------ 53 | 54 | class BullDog(Dog): 55 | def bark(self): 56 | return super().bark() + ", GRR" 57 | # Note, super() is much simpler to use in Python 3! 58 | 59 | def test_subclasses_can_invoke_parent_behavior_via_super(self): 60 | ralph = self.BullDog("Ralph") 61 | self.assertEqual(__, ralph.bark()) 62 | 63 | # ------------------------------------------------------------------ 64 | 65 | class GreatDane(Dog): 66 | def growl(self): 67 | return super().bark() + ", GROWL" 68 | 69 | def test_super_works_across_methods(self): 70 | george = self.GreatDane("George") 71 | self.assertEqual(__, george.growl()) 72 | 73 | # --------------------------------------------------------- 74 | 75 | class Pug(Dog): 76 | def __init__(self, name): 77 | pass 78 | 79 | class Greyhound(Dog): 80 | def __init__(self, name): 81 | super().__init__(name) 82 | 83 | def test_base_init_does_not_get_called_automatically(self): 84 | snoopy = self.Pug("Snoopy") 85 | with self.assertRaises(___): name = snoopy.name 86 | 87 | def test_base_init_has_to_be_called_explicitly(self): 88 | boxer = self.Greyhound("Boxer") 89 | self.assertEqual(__, boxer.name) 90 | -------------------------------------------------------------------------------- /koans/about_iteration.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | class AboutIteration(Koan): 7 | 8 | def test_iterators_are_a_type(self): 9 | it = iter(range(1,6)) 10 | 11 | total = 0 12 | 13 | for num in it: 14 | total += num 15 | 16 | self.assertEqual(__ , total) 17 | 18 | def test_iterating_with_next(self): 19 | stages = iter(['alpha','beta','gamma']) 20 | 21 | try: 22 | self.assertEqual(__, next(stages)) 23 | next(stages) 24 | self.assertEqual(__, next(stages)) 25 | next(stages) 26 | except StopIteration as ex: 27 | err_msg = 'Ran out of iterations' 28 | 29 | self.assertRegex(err_msg, __) 30 | 31 | # ------------------------------------------------------------------ 32 | 33 | def add_ten(self, item): 34 | return item + 10 35 | 36 | def test_map_transforms_elements_of_a_list(self): 37 | seq = [1, 2, 3] 38 | mapped_seq = list() 39 | 40 | mapping = map(self.add_ten, seq) 41 | 42 | self.assertNotEqual(list, mapping.__class__) 43 | self.assertEqual(__, mapping.__class__) 44 | # In Python 3 built in iterator funcs return iterable view objects 45 | # instead of lists 46 | 47 | for item in mapping: 48 | mapped_seq.append(item) 49 | 50 | self.assertEqual(__, mapped_seq) 51 | 52 | # Note, iterator methods actually return objects of iter type in 53 | # python 3. In python 2 map() would give you a list. 54 | 55 | def test_filter_selects_certain_items_from_a_list(self): 56 | def is_even(item): 57 | return (item % 2) == 0 58 | 59 | seq = [1, 2, 3, 4, 5, 6] 60 | even_numbers = list() 61 | 62 | for item in filter(is_even, seq): 63 | even_numbers.append(item) 64 | 65 | self.assertEqual(__, even_numbers) 66 | 67 | def test_filter_returns_all_items_matching_criterion(self): 68 | def is_big_name(item): 69 | return len(item) > 4 70 | 71 | names = ["Jim", "Bill", "Clarence", "Doug", "Eli", "Elizabeth"] 72 | iterator = filter(is_big_name, names) 73 | 74 | self.assertEqual(__, next(iterator)) 75 | self.assertEqual(__, next(iterator)) 76 | 77 | try: 78 | next(iterator) 79 | pass 80 | except StopIteration: 81 | msg = 'Ran out of big names' 82 | 83 | self.assertEquals(__, msg) 84 | 85 | # ------------------------------------------------------------------ 86 | 87 | def add(self,accum,item): 88 | return accum + item 89 | 90 | def multiply(self,accum,item): 91 | return accum * item 92 | 93 | def test_reduce_will_blow_your_mind(self): 94 | import functools 95 | # As of Python 3 reduce() has been demoted from a builtin function 96 | # to the functools module. 97 | 98 | result = functools.reduce(self.add, [2, 3, 4]) 99 | self.assertEqual(__, result.__class__) 100 | # Reduce() syntax is same as Python 2 101 | 102 | self.assertEqual(__, result) 103 | 104 | result2 = functools.reduce(self.multiply, [2, 3, 4], 1) 105 | self.assertEqual(__, result2) 106 | 107 | # Extra Credit: 108 | # Describe in your own words what reduce does. 109 | 110 | # ------------------------------------------------------------------ 111 | 112 | def test_use_pass_for_iterations_with_no_body(self): 113 | for num in range(1,5): 114 | pass 115 | 116 | self.assertEqual(__, num) 117 | 118 | # ------------------------------------------------------------------ 119 | 120 | def test_all_iteration_methods_work_on_any_sequence_not_just_lists(self): 121 | # Ranges are an iterable sequence 122 | result = map(self.add_ten, range(1,4)) 123 | self.assertEqual(__, list(result)) 124 | 125 | def test_lines_in_a_file_are_iterable_sequences_too(self): 126 | def make_upcase(line): 127 | return line.strip().title() 128 | 129 | file = open("example_file.txt") 130 | upcase_lines = map(make_upcase, file.readlines()) 131 | self.assertEqual(__, list(upcase_lines)) 132 | file.close() 133 | -------------------------------------------------------------------------------- /koans/about_lambdas.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 5 | # Based slightly on the lambdas section of AboutBlocks in the Ruby Koans 6 | # 7 | 8 | from runner.koan import * 9 | 10 | class AboutLambdas(Koan): 11 | def test_lambdas_can_be_assigned_to_variables_and_called_explicitly(self): 12 | add_one = lambda n: n + 1 13 | self.assertEqual(__, add_one(10)) 14 | 15 | # ------------------------------------------------------------------ 16 | 17 | def make_order(self, order): 18 | return lambda qty: str(qty) + " " + order + "s" 19 | 20 | def test_accessing_lambda_via_assignment(self): 21 | sausages = self.make_order('sausage') 22 | eggs = self.make_order('egg') 23 | 24 | self.assertEqual(__, sausages(3)) 25 | self.assertEqual(__, eggs(2)) 26 | 27 | def test_accessing_lambda_without_assignment(self): 28 | self.assertEqual(__, self.make_order('spam')(39823)) 29 | -------------------------------------------------------------------------------- /koans/about_list_assignments.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 5 | # Based on AboutArrayAssignments in the Ruby Koans 6 | # 7 | 8 | from runner.koan import * 9 | 10 | class AboutListAssignments(Koan): 11 | def test_non_parallel_assignment(self): 12 | names = ["John", "Smith"] 13 | self.assertEqual(__, names) 14 | 15 | def test_parallel_assignments(self): 16 | first_name, last_name = ["John", "Smith"] 17 | self.assertEqual(__, first_name) 18 | self.assertEqual(__, last_name) 19 | 20 | def test_parallel_assignments_with_extra_values(self): 21 | title, *first_names, last_name = ["Sir", "Ricky", "Bobby", "Worthington"] 22 | self.assertEqual(__, title) 23 | self.assertEqual(__, first_names) 24 | self.assertEqual(__, last_name) 25 | 26 | def test_parallel_assignments_with_fewer_values(self): 27 | title, *first_names, last_name = ["Mr", "Bond"] 28 | self.assertEqual(__, title) 29 | self.assertEqual(__, first_names) 30 | self.assertEqual(__, last_name) 31 | 32 | def test_parallel_assignments_with_sublists(self): 33 | first_name, last_name = [["Willie", "Rae"], "Johnson"] 34 | self.assertEqual(__, first_name) 35 | self.assertEqual(__, last_name) 36 | 37 | def test_swapping_with_parallel_assignment(self): 38 | first_name = "Roy" 39 | last_name = "Rob" 40 | first_name, last_name = last_name, first_name 41 | self.assertEqual(__, first_name) 42 | self.assertEqual(__, last_name) 43 | 44 | -------------------------------------------------------------------------------- /koans/about_lists.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 5 | # Based on AboutArrays in the Ruby Koans 6 | # 7 | 8 | from runner.koan import * 9 | 10 | class AboutLists(Koan): 11 | def test_creating_lists(self): 12 | empty_list = list() 13 | self.assertEqual(list, type(empty_list)) 14 | self.assertEqual(__, len(empty_list)) 15 | 16 | def test_list_literals(self): 17 | nums = list() 18 | self.assertEqual([], nums) 19 | 20 | nums[0:] = [1] 21 | self.assertEqual([1], nums) 22 | 23 | nums[1:] = [2] 24 | self.assertListEqual([1, __], nums) 25 | 26 | nums.append(333) 27 | self.assertListEqual([1, 2, __], nums) 28 | 29 | def test_accessing_list_elements(self): 30 | noms = ['peanut', 'butter', 'and', 'jelly'] 31 | 32 | self.assertEqual(__, noms[0]) 33 | self.assertEqual(__, noms[3]) 34 | self.assertEqual(__, noms[-1]) 35 | self.assertEqual(__, noms[-3]) 36 | 37 | def test_slicing_lists(self): 38 | noms = ['peanut', 'butter', 'and', 'jelly'] 39 | 40 | self.assertEqual(__, noms[0:1]) 41 | self.assertEqual(__, noms[0:2]) 42 | self.assertEqual(__, noms[2:2]) 43 | self.assertEqual(__, noms[2:20]) 44 | self.assertEqual(__, noms[4:0]) 45 | self.assertEqual(__, noms[4:100]) 46 | self.assertEqual(__, noms[5:0]) 47 | 48 | def test_slicing_to_the_edge(self): 49 | noms = ['peanut', 'butter', 'and', 'jelly'] 50 | 51 | self.assertEqual(__, noms[2:]) 52 | self.assertEqual(__, noms[:2]) 53 | 54 | def test_lists_and_ranges(self): 55 | self.assertEqual(range, type(range(5))) 56 | self.assertNotEqual([1, 2, 3, 4, 5], range(1,6)) 57 | self.assertEqual(__, list(range(5))) 58 | self.assertEqual(__, list(range(5, 9))) 59 | 60 | def test_ranges_with_steps(self): 61 | self.assertEqual(__, list(range(5, 3, -1))) 62 | self.assertEqual(__, list(range(0, 8, 2))) 63 | self.assertEqual(__, list(range(1, 8, 3))) 64 | self.assertEqual(__, list(range(5, -7, -4))) 65 | self.assertEqual(__, list(range(5, -8, -4))) 66 | 67 | def test_insertions(self): 68 | knight = ['you', 'shall', 'pass'] 69 | knight.insert(2, 'not') 70 | self.assertEqual(__, knight) 71 | 72 | knight.insert(0, 'Arthur') 73 | self.assertEqual(__, knight) 74 | 75 | def test_popping_lists(self): 76 | stack = [10, 20, 30, 40] 77 | stack.append('last') 78 | 79 | self.assertEqual(__, stack) 80 | 81 | popped_value = stack.pop() 82 | self.assertEqual(__, popped_value) 83 | self.assertEqual(__, stack) 84 | 85 | popped_value = stack.pop(1) 86 | self.assertEqual(__, popped_value) 87 | self.assertEqual(__, stack) 88 | 89 | # Notice that there is a "pop" but no "push" in python? 90 | 91 | # Part of the Python philosophy is that there ideally should be one and 92 | # only one way of doing anything. A 'push' is the same as an 'append'. 93 | 94 | # To learn more about this try typing "import this" from the python 95 | # console... ;) 96 | 97 | def test_making_queues(self): 98 | queue = [1, 2] 99 | queue.append('last') 100 | 101 | self.assertEqual(__, queue) 102 | 103 | popped_value = queue.pop(0) 104 | self.assertEqual(__, popped_value) 105 | self.assertEqual(__, queue) 106 | 107 | # Note, popping from the left hand side of a list is 108 | # inefficient. Use collections.deque instead. 109 | 110 | -------------------------------------------------------------------------------- /koans/about_method_bindings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | def function(): 7 | return "pineapple" 8 | 9 | def function2(): 10 | return "tractor" 11 | 12 | class Class: 13 | def method(self): 14 | return "parrot" 15 | 16 | class AboutMethodBindings(Koan): 17 | def test_methods_are_bound_to_an_object(self): 18 | obj = Class() 19 | self.assertEqual(__, obj.method.__self__ == obj) 20 | 21 | def test_methods_are_also_bound_to_a_function(self): 22 | obj = Class() 23 | self.assertEqual(__, obj.method()) 24 | self.assertEqual(__, obj.method.__func__(obj)) 25 | 26 | def test_functions_have_attributes(self): 27 | obj = Class() 28 | self.assertEqual(__, len(dir(function))) 29 | self.assertEqual(__, dir(function) == dir(obj.method.__func__)) 30 | 31 | def test_methods_have_different_attributes(self): 32 | obj = Class() 33 | self.assertEqual(__, len(dir(obj.method))) 34 | 35 | def test_setting_attributes_on_an_unbound_function(self): 36 | function.cherries = 3 37 | self.assertEqual(__, function.cherries) 38 | 39 | def test_setting_attributes_on_a_bound_method_directly(self): 40 | obj = Class() 41 | with self.assertRaises(___): obj.method.cherries = 3 42 | 43 | def test_setting_attributes_on_methods_by_accessing_the_inner_function(self): 44 | obj = Class() 45 | obj.method.__func__.cherries = 3 46 | self.assertEqual(__, obj.method.cherries) 47 | 48 | def test_functions_can_have_inner_functions(self): 49 | function2.get_fruit = function 50 | self.assertEqual(__, function2.get_fruit()) 51 | 52 | def test_inner_functions_are_unbound(self): 53 | function2.get_fruit = function 54 | with self.assertRaises(___): cls = function2.get_fruit.__self__ 55 | 56 | # ------------------------------------------------------------------ 57 | 58 | class BoundClass: 59 | def __get__(self, obj, cls): 60 | return (self, obj, cls) 61 | 62 | binding = BoundClass() 63 | 64 | def test_get_descriptor_resolves_attribute_binding(self): 65 | bound_obj, binding_owner, owner_type = self.binding 66 | # Look at BoundClass.__get__(): 67 | # bound_obj = self 68 | # binding_owner = obj 69 | # owner_type = cls 70 | 71 | self.assertEqual(__, bound_obj.__class__.__name__) 72 | self.assertEqual(__, binding_owner.__class__.__name__) 73 | self.assertEqual(AboutMethodBindings, owner_type) 74 | 75 | # ------------------------------------------------------------------ 76 | 77 | class SuperColor: 78 | def __init__(self): 79 | self.choice = None 80 | 81 | def __set__(self, obj, val): 82 | self.choice = val 83 | 84 | color = SuperColor() 85 | 86 | def test_set_descriptor_changes_behavior_of_attribute_assignment(self): 87 | self.assertEqual(None, self.color.choice) 88 | self.color = 'purple' 89 | self.assertEqual(__, self.color.choice) 90 | 91 | -------------------------------------------------------------------------------- /koans/about_methods.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 5 | # Partially based on AboutMethods in the Ruby Koans 6 | # 7 | 8 | from runner.koan import * 9 | 10 | def my_global_function(a,b): 11 | return a + b 12 | 13 | class AboutMethods(Koan): 14 | def test_calling_a_global_function(self): 15 | self.assertEqual(__, my_global_function(2,3)) 16 | 17 | # NOTE: Wrong number of arguments is not a SYNTAX error, but a 18 | # runtime error. 19 | def test_calling_functions_with_wrong_number_of_arguments(self): 20 | try: 21 | my_global_function() 22 | except TypeError as exception: 23 | msg = exception.args[0] 24 | 25 | # Note, the text comparison works for Python 3.2 26 | # It has changed in the past and may change in the future 27 | self.assertRegex(msg, 28 | r'my_global_function\(\) missing 2 required positional arguments') 29 | 30 | try: 31 | my_global_function(1, 2, 3) 32 | except Exception as e: 33 | msg = e.args[0] 34 | 35 | # Note, watch out for parenthesis. They need slashes in front! 36 | self.assertRegex(msg, __) 37 | 38 | # ------------------------------------------------------------------ 39 | 40 | def pointless_method(self, a, b): 41 | sum = a + b 42 | 43 | def test_which_does_not_return_anything(self): 44 | self.assertEqual(__, self.pointless_method(1, 2)) 45 | # Notice that methods accessed from class scope do not require 46 | # you to pass the first "self" argument? 47 | 48 | # ------------------------------------------------------------------ 49 | 50 | def method_with_defaults(self, a, b='default_value'): 51 | return [a, b] 52 | 53 | def test_calling_with_default_values(self): 54 | self.assertEqual(__, self.method_with_defaults(1)) 55 | self.assertEqual(__, self.method_with_defaults(1, 2)) 56 | 57 | # ------------------------------------------------------------------ 58 | 59 | def method_with_var_args(self, *args): 60 | return args 61 | 62 | def test_calling_with_variable_arguments(self): 63 | self.assertEqual(__, self.method_with_var_args()) 64 | self.assertEqual(('one',), self.method_with_var_args('one')) 65 | self.assertEqual(__, self.method_with_var_args('one', 'two')) 66 | 67 | # ------------------------------------------------------------------ 68 | 69 | def function_with_the_same_name(self, a, b): 70 | return a + b 71 | 72 | def test_functions_without_self_arg_are_global_functions(self): 73 | def function_with_the_same_name(a, b): 74 | return a * b 75 | 76 | self.assertEqual(__, function_with_the_same_name(3,4)) 77 | 78 | def test_calling_methods_in_same_class_with_explicit_receiver(self): 79 | def function_with_the_same_name(a, b): 80 | return a * b 81 | 82 | self.assertEqual(__, self.function_with_the_same_name(3,4)) 83 | 84 | # ------------------------------------------------------------------ 85 | 86 | def another_method_with_the_same_name(self): 87 | return 10 88 | 89 | link_to_overlapped_method = another_method_with_the_same_name 90 | 91 | def another_method_with_the_same_name(self): 92 | return 42 93 | 94 | def test_that_old_methods_are_hidden_by_redefinitions(self): 95 | self.assertEqual(__, self.another_method_with_the_same_name()) 96 | 97 | def test_that_overlapped_method_is_still_there(self): 98 | self.assertEqual(__, self.link_to_overlapped_method()) 99 | 100 | # ------------------------------------------------------------------ 101 | 102 | def empty_method(self): 103 | pass 104 | 105 | def test_methods_that_do_nothing_need_to_use_pass_as_a_filler(self): 106 | self.assertEqual(__, self.empty_method()) 107 | 108 | def test_pass_does_nothing_at_all(self): 109 | "You" 110 | "shall" 111 | "not" 112 | pass 113 | self.assertEqual(____, "Still got to this line" != None) 114 | 115 | # ------------------------------------------------------------------ 116 | 117 | def one_line_method(self): return 'Madagascar' 118 | 119 | def test_no_indentation_required_for_one_line_statement_bodies(self): 120 | self.assertEqual(__, self.one_line_method()) 121 | 122 | # ------------------------------------------------------------------ 123 | 124 | def method_with_documentation(self): 125 | "A string placed at the beginning of a function is used for documentation" 126 | return "ok" 127 | 128 | def test_the_documentation_can_be_viewed_with_the_doc_method(self): 129 | self.assertRegex(self.method_with_documentation.__doc__, __) 130 | 131 | # ------------------------------------------------------------------ 132 | 133 | class Dog: 134 | def name(self): 135 | return "Fido" 136 | 137 | def _tail(self): 138 | # Prefixing a method with an underscore implies private scope 139 | return "wagging" 140 | 141 | def __password(self): 142 | return 'password' # Genius! 143 | 144 | def test_calling_methods_in_other_objects(self): 145 | rover = self.Dog() 146 | self.assertEqual(__, rover.name()) 147 | 148 | def test_private_access_is_implied_but_not_enforced(self): 149 | rover = self.Dog() 150 | 151 | # This is a little rude, but legal 152 | self.assertEqual(__, rover._tail()) 153 | 154 | def test_attributes_with_double_underscore_prefixes_are_subject_to_name_mangling(self): 155 | rover = self.Dog() 156 | with self.assertRaises(___): password = rover.__password() 157 | 158 | # But this still is! 159 | self.assertEqual(__, rover._Dog__password()) 160 | 161 | # Name mangling exists to avoid name clash issues when subclassing. 162 | # It is not for providing effective access protection 163 | -------------------------------------------------------------------------------- /koans/about_modules.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 5 | # This is very different to AboutModules in Ruby Koans 6 | # Our AboutMultipleInheritance class is a little more comparable 7 | # 8 | 9 | from runner.koan import * 10 | 11 | from .another_local_module import * 12 | from .local_module_with_all_defined import * 13 | 14 | 15 | class AboutModules(Koan): 16 | def test_importing_other_python_scripts_as_modules(self): 17 | from . import local_module # local_module.py 18 | 19 | duck = local_module.Duck() 20 | self.assertEqual(__, duck.name) 21 | 22 | def test_importing_attributes_from_classes_using_from_keyword(self): 23 | from .local_module import Duck 24 | 25 | duck = Duck() # no module qualifier needed this time 26 | self.assertEqual(__, duck.name) 27 | 28 | def test_we_can_import_multiple_items_at_once(self): 29 | from . import jims, joes 30 | 31 | jims_dog = jims.Dog() 32 | joes_dog = joes.Dog() 33 | self.assertEqual(__, jims_dog.identify()) 34 | self.assertEqual(__, joes_dog.identify()) 35 | 36 | def test_importing_all_module_attributes_at_once(self): 37 | """ 38 | importing all attributes at once is done like so: 39 | from .another_local_module import * 40 | The import wildcard cannot be used from within classes or functions. 41 | """ 42 | 43 | goose = Goose() 44 | hamster = Hamster() 45 | 46 | self.assertEqual(__, goose.name) 47 | self.assertEqual(__, hamster.name) 48 | 49 | def test_modules_hide_attributes_prefixed_by_underscores(self): 50 | with self.assertRaises(___): 51 | private_squirrel = _SecretSquirrel() 52 | 53 | def test_private_attributes_are_still_accessible_in_modules(self): 54 | from .local_module import Duck # local_module.py 55 | 56 | duck = Duck() 57 | self.assertEqual(__, duck._password) 58 | # module level attribute hiding doesn't affect class attributes 59 | # (unless the class itself is hidden). 60 | 61 | def test_a_module_can_limit_wildcard_imports(self): 62 | """ 63 | Examine results of: 64 | from .local_module_with_all_defined import * 65 | """ 66 | 67 | # 'Goat' is on the __all__ list 68 | goat = Goat() 69 | self.assertEqual(__, goat.name) 70 | 71 | # How about velociraptors? 72 | lizard = _Velociraptor() 73 | self.assertEqual(__, lizard.name) 74 | 75 | # SecretDuck? Never heard of her! 76 | with self.assertRaises(___): 77 | duck = SecretDuck() 78 | -------------------------------------------------------------------------------- /koans/about_monkey_patching.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 5 | # Related to AboutOpenClasses in the Ruby Koans 6 | # 7 | 8 | from runner.koan import * 9 | 10 | class AboutMonkeyPatching(Koan): 11 | class Dog: 12 | def bark(self): 13 | return "WOOF" 14 | 15 | def test_as_defined_dogs_do_bark(self): 16 | fido = self.Dog() 17 | self.assertEqual(__, fido.bark()) 18 | 19 | # ------------------------------------------------------------------ 20 | 21 | # Add a new method to an existing class. 22 | def test_after_patching_dogs_can_both_wag_and_bark(self): 23 | def wag(self): return "HAPPY" 24 | self.Dog.wag = wag 25 | 26 | fido = self.Dog() 27 | self.assertEqual(__, fido.wag()) 28 | self.assertEqual(__, fido.bark()) 29 | 30 | # ------------------------------------------------------------------ 31 | 32 | def test_most_built_in_classes_cannot_be_monkey_patched(self): 33 | try: 34 | int.is_even = lambda self: (self % 2) == 0 35 | except Exception as ex: 36 | err_msg = ex.args[0] 37 | 38 | self.assertRegex(err_msg, __) 39 | 40 | # ------------------------------------------------------------------ 41 | 42 | class MyInt(int): pass 43 | 44 | def test_subclasses_of_built_in_classes_can_be_be_monkey_patched(self): 45 | self.MyInt.is_even = lambda self: (self % 2) == 0 46 | 47 | self.assertEqual(__, self.MyInt(1).is_even()) 48 | self.assertEqual(__, self.MyInt(2).is_even()) 49 | -------------------------------------------------------------------------------- /koans/about_multiple_inheritance.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 5 | # Slightly based on AboutModules in the Ruby Koans 6 | # 7 | 8 | from runner.koan import * 9 | 10 | class AboutMultipleInheritance(Koan): 11 | class Nameable: 12 | def __init__(self): 13 | self._name = None 14 | 15 | def set_name(self, new_name): 16 | self._name = new_name 17 | 18 | def here(self): 19 | return "In Nameable class" 20 | 21 | class Animal: 22 | def legs(self): 23 | return 4 24 | 25 | def can_climb_walls(self): 26 | return False 27 | 28 | def here(self): 29 | return "In Animal class" 30 | 31 | class Pig(Animal): 32 | def __init__(self): 33 | super().__init__() 34 | self._name = "Jasper" 35 | 36 | @property 37 | def name(self): 38 | return self._name 39 | 40 | def speak(self): 41 | return "OINK" 42 | 43 | def color(self): 44 | return 'pink' 45 | 46 | def here(self): 47 | return "In Pig class" 48 | 49 | class Spider(Animal): 50 | def __init__(self): 51 | super().__init__() 52 | self._name = "Boris" 53 | 54 | def can_climb_walls(self): 55 | return True 56 | 57 | def legs(self): 58 | return 8 59 | 60 | def color(self): 61 | return 'black' 62 | 63 | def here(self): 64 | return "In Spider class" 65 | 66 | class Spiderpig(Pig, Spider, Nameable): 67 | def __init__(self): 68 | super(AboutMultipleInheritance.Pig, self).__init__() 69 | super(AboutMultipleInheritance.Nameable, self).__init__() 70 | self._name = "Jeff" 71 | 72 | def speak(self): 73 | return "This looks like a job for Spiderpig!" 74 | 75 | def here(self): 76 | return "In Spiderpig class" 77 | 78 | # 79 | # Hierarchy: 80 | # Animal 81 | # / \ 82 | # Pig Spider Nameable 83 | # \ | / 84 | # Spiderpig 85 | # 86 | # ------------------------------------------------------------------ 87 | 88 | def test_normal_methods_are_available_in_the_object(self): 89 | jeff = self.Spiderpig() 90 | self.assertRegex(jeff.speak(), __) 91 | 92 | def test_base_class_methods_are_also_available_in_the_object(self): 93 | jeff = self.Spiderpig() 94 | try: 95 | jeff.set_name("Rover") 96 | except: 97 | self.fail("This should not happen") 98 | self.assertEqual(__, jeff.can_climb_walls()) 99 | 100 | def test_base_class_methods_can_affect_instance_variables_in_the_object(self): 101 | jeff = self.Spiderpig() 102 | self.assertEqual(__, jeff.name) 103 | 104 | jeff.set_name("Rover") 105 | self.assertEqual(__, jeff.name) 106 | 107 | def test_left_hand_side_inheritance_tends_to_be_higher_priority(self): 108 | jeff = self.Spiderpig() 109 | self.assertEqual(__, jeff.color()) 110 | 111 | def test_super_class_methods_are_higher_priority_than_super_super_classes(self): 112 | jeff = self.Spiderpig() 113 | self.assertEqual(__, jeff.legs()) 114 | 115 | def test_we_can_inspect_the_method_resolution_order(self): 116 | # 117 | # MRO = Method Resolution Order 118 | # 119 | mro = type(self.Spiderpig()).mro() 120 | self.assertEqual('Spiderpig', mro[0].__name__) 121 | self.assertEqual('Pig', mro[1].__name__) 122 | self.assertEqual(__, mro[2].__name__) 123 | self.assertEqual(__, mro[3].__name__) 124 | self.assertEqual(__, mro[4].__name__) 125 | self.assertEqual(__, mro[5].__name__) 126 | 127 | def test_confirm_the_mro_controls_the_calling_order(self): 128 | jeff = self.Spiderpig() 129 | self.assertRegex(jeff.here(), 'Spiderpig') 130 | 131 | next = super(AboutMultipleInheritance.Spiderpig, jeff) 132 | self.assertRegex(next.here(), 'Pig') 133 | 134 | next = super(AboutMultipleInheritance.Pig, jeff) 135 | self.assertRegex(next.here(), __) 136 | 137 | # Hang on a minute?!? That last class name might be a super class of 138 | # the 'jeff' object, but its hardly a superclass of Pig, is it? 139 | # 140 | # To avoid confusion it may help to think of super() as next_mro(). 141 | -------------------------------------------------------------------------------- /koans/about_none.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 5 | # Based on AboutNil in the Ruby Koans 6 | # 7 | 8 | from runner.koan import * 9 | 10 | class AboutNone(Koan): 11 | 12 | def test_none_is_an_object(self): 13 | "Unlike NULL in a lot of languages" 14 | self.assertEqual(__, isinstance(None, object)) 15 | 16 | def test_none_is_universal(self): 17 | "There is only one None" 18 | self.assertEqual(____, None is None) 19 | 20 | def test_what_exception_do_you_get_when_calling_nonexistent_methods(self): 21 | """ 22 | What is the Exception that is thrown when you call a method that does 23 | not exist? 24 | 25 | Hint: launch python command console and try the code in the block below. 26 | 27 | Don't worry about what 'try' and 'except' do, we'll talk about this later 28 | """ 29 | try: 30 | None.some_method_none_does_not_know_about() 31 | except Exception as ex: 32 | ex2 = ex 33 | 34 | # What exception has been caught? 35 | # 36 | # Need a recap on how to evaluate __class__ attributes? 37 | # 38 | # https://github.com/gregmalcolm/python_koans/wiki/Class-Attribute 39 | 40 | self.assertEqual(__, ex2.__class__) 41 | 42 | # What message was attached to the exception? 43 | # (HINT: replace __ with part of the error message.) 44 | self.assertRegex(ex2.args[0], __) 45 | 46 | def test_none_is_distinct(self): 47 | """ 48 | None is distinct from other things which are False. 49 | """ 50 | self.assertEqual(__, None is not 0) 51 | self.assertEqual(__, None is not False) 52 | -------------------------------------------------------------------------------- /koans/about_packages.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 5 | # This is very different to AboutModules in Ruby Koans 6 | # Our AboutMultipleInheritance class is a little more comparable 7 | # 8 | 9 | from runner.koan import * 10 | 11 | # 12 | # Package hierarchy of Python Koans project: 13 | # 14 | # contemplate_koans.py 15 | # koans/ 16 | # __init__.py 17 | # about_asserts.py 18 | # about_attribute_access.py 19 | # about_class_attributes.py 20 | # about_classes.py 21 | # ... 22 | # a_package_folder/ 23 | # __init__.py 24 | # a_module.py 25 | 26 | class AboutPackages(Koan): 27 | def test_subfolders_can_form_part_of_a_module_package(self): 28 | # Import ./a_package_folder/a_module.py 29 | from .a_package_folder.a_module import Duck 30 | 31 | duck = Duck() 32 | self.assertEqual(__, duck.name) 33 | 34 | def test_subfolders_become_modules_if_they_have_an_init_module(self): 35 | # Import ./a_package_folder/__init__.py 36 | from .a_package_folder import an_attribute 37 | 38 | self.assertEqual(__, an_attribute) 39 | 40 | # ------------------------------------------------------------------ 41 | 42 | def test_use_absolute_imports_to_import_upper_level_modules(self): 43 | # Import /contemplate_koans.py 44 | import contemplate_koans 45 | 46 | self.assertEqual(__, contemplate_koans.__name__) 47 | 48 | # contemplate_koans.py is the root module in this package because it's 49 | # the first python module called in koans. 50 | # 51 | # If contemplate_koans.py was based in a_package_folder that would be 52 | # the root folder, which would make reaching the koans folder 53 | # almost impossible. So always leave the starting python script in 54 | # a folder which can reach everything else. 55 | 56 | def test_import_a_module_in_a_subfolder_folder_using_an_absolute_path(self): 57 | # Import contemplate_koans.py/koans/a_package_folder/a_module.py 58 | from koans.a_package_folder.a_module import Duck 59 | 60 | self.assertEqual(__, Duck.__module__) 61 | -------------------------------------------------------------------------------- /koans/about_proxy_object_project.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Project: Create a Proxy Class 5 | # 6 | # In this assignment, create a proxy class (one is started for you 7 | # below). You should be able to initialize the proxy object with any 8 | # object. Any attributes called on the proxy object should be forwarded 9 | # to the target object. As each attribute call is sent, the proxy should 10 | # record the name of the attribute sent. 11 | # 12 | # The proxy class is started for you. You will need to add a method 13 | # missing handler and any other supporting methods. The specification 14 | # of the Proxy class is given in the AboutProxyObjectProject koan. 15 | 16 | # Note: This is a bit trickier than its Ruby Koans counterpart, but you 17 | # can do it! 18 | 19 | from runner.koan import * 20 | 21 | class Proxy: 22 | def __init__(self, target_object): 23 | # WRITE CODE HERE 24 | 25 | #initialize '_obj' attribute last. Trust me on this! 26 | self._obj = target_object 27 | 28 | # WRITE CODE HERE 29 | 30 | # The proxy object should pass the following Koan: 31 | # 32 | class AboutProxyObjectProject(Koan): 33 | def test_proxy_method_returns_wrapped_object(self): 34 | # NOTE: The Television class is defined below 35 | tv = Proxy(Television()) 36 | 37 | self.assertTrue(isinstance(tv, Proxy)) 38 | 39 | def test_tv_methods_still_perform_their_function(self): 40 | tv = Proxy(Television()) 41 | 42 | tv.channel = 10 43 | tv.power() 44 | 45 | self.assertEqual(10, tv.channel) 46 | self.assertTrue(tv.is_on()) 47 | 48 | def test_proxy_records_messages_sent_to_tv(self): 49 | tv = Proxy(Television()) 50 | 51 | tv.power() 52 | tv.channel = 10 53 | 54 | self.assertEqual(['power', 'channel'], tv.messages()) 55 | 56 | def test_proxy_handles_invalid_messages(self): 57 | tv = Proxy(Television()) 58 | 59 | with self.assertRaises(AttributeError): 60 | tv.no_such_method() 61 | 62 | 63 | def test_proxy_reports_methods_have_been_called(self): 64 | tv = Proxy(Television()) 65 | 66 | tv.power() 67 | tv.power() 68 | 69 | self.assertTrue(tv.was_called('power')) 70 | self.assertFalse(tv.was_called('channel')) 71 | 72 | def test_proxy_counts_method_calls(self): 73 | tv = Proxy(Television()) 74 | 75 | tv.power() 76 | tv.channel = 48 77 | tv.power() 78 | 79 | self.assertEqual(2, tv.number_of_times_called('power')) 80 | self.assertEqual(1, tv.number_of_times_called('channel')) 81 | self.assertEqual(0, tv.number_of_times_called('is_on')) 82 | 83 | def test_proxy_can_record_more_than_just_tv_objects(self): 84 | proxy = Proxy("Py Ohio 2010") 85 | 86 | result = proxy.upper() 87 | 88 | self.assertEqual("PY OHIO 2010", result) 89 | 90 | result = proxy.split() 91 | 92 | self.assertEqual(["Py", "Ohio", "2010"], result) 93 | self.assertEqual(['upper', 'split'], proxy.messages()) 94 | 95 | # ==================================================================== 96 | # The following code is to support the testing of the Proxy class. No 97 | # changes should be necessary to anything below this comment. 98 | 99 | # Example class using in the proxy testing above. 100 | class Television: 101 | def __init__(self): 102 | self._channel = None 103 | self._power = None 104 | 105 | @property 106 | def channel(self): 107 | return self._channel 108 | 109 | @channel.setter 110 | def channel(self, value): 111 | self._channel = value 112 | 113 | def power(self): 114 | if self._power == 'on': 115 | self._power = 'off' 116 | else: 117 | self._power = 'on' 118 | 119 | def is_on(self): 120 | return self._power == 'on' 121 | 122 | # Tests for the Television class. All of theses tests should pass. 123 | class TelevisionTest(Koan): 124 | def test_it_turns_on(self): 125 | tv = Television() 126 | 127 | tv.power() 128 | self.assertTrue(tv.is_on()) 129 | 130 | def test_it_also_turns_off(self): 131 | tv = Television() 132 | 133 | tv.power() 134 | tv.power() 135 | 136 | self.assertFalse(tv.is_on()) 137 | 138 | def test_edge_case_on_off(self): 139 | tv = Television() 140 | 141 | tv.power() 142 | tv.power() 143 | tv.power() 144 | 145 | self.assertTrue(tv.is_on()) 146 | 147 | tv.power() 148 | 149 | self.assertFalse(tv.is_on()) 150 | 151 | def test_can_set_the_channel(self): 152 | tv = Television() 153 | 154 | tv.channel = 11 155 | self.assertEqual(11, tv.channel) 156 | -------------------------------------------------------------------------------- /koans/about_regex.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | import re 7 | 8 | 9 | class AboutRegex(Koan): 10 | """ 11 | These koans are based on Ben's book: Regular Expressions in 10 12 | minutes. I found this book very useful, so I decided to write 13 | a koan file in order to practice everything it taught me. 14 | http://www.forta.com/books/0672325667/ 15 | """ 16 | 17 | def test_matching_literal_text(self): 18 | """ 19 | Lesson 1 Matching Literal String 20 | """ 21 | string = "Hello, my name is Felix and these koans are based " + \ 22 | "on Ben's book: Regular Expressions in 10 minutes." 23 | m = re.search(__, string) 24 | self.assertTrue( 25 | m and m.group(0) and 26 | m.group(0) == 'Felix', 27 | "I want my name") 28 | 29 | def test_matching_literal_text_how_many(self): 30 | """ 31 | Lesson 1 -- How many matches? 32 | 33 | The default behaviour of most regular expression engines is 34 | to return just the first match. In python you have the 35 | following options: 36 | 37 | match() --> Determine if the RE matches at the 38 | beginning of the string. 39 | search() --> Scan through a string, looking for any 40 | location where this RE matches. 41 | findall() --> Find all substrings where the RE 42 | matches, and return them as a list. 43 | finditer() --> Find all substrings where the RE 44 | matches, and return them as an iterator. 45 | """ 46 | string = ("Hello, my name is Felix and these koans are based " + 47 | "on Ben's book: Regular Expressions in 10 minutes. " + 48 | "Repeat My name is Felix") 49 | m = re.match('Felix', string) # TIP: match may not be the best option 50 | 51 | # I want to know how many times my name appears 52 | self.assertEqual(m, __) 53 | 54 | def test_matching_literal_text_not_case_sensitivity(self): 55 | """ 56 | Lesson 1 -- Matching Literal String non case sensitivity. 57 | Most regex implementations also support matches that are not 58 | case sensitive. In python you can use re.IGNORECASE, in 59 | Javascript you can specify the optional i flag. In Ben's 60 | book you can see more languages. 61 | 62 | """ 63 | string = "Hello, my name is Felix or felix and this koan " + \ 64 | "is based on Ben's book: Regular Expressions in 10 minutes." 65 | 66 | self.assertEqual(re.findall("felix", string), __) 67 | self.assertEqual(re.findall("felix", string, re.IGNORECASE), __) 68 | 69 | def test_matching_any_character(self): 70 | """ 71 | Lesson 1: Matching any character 72 | 73 | `.` matches any character: alphabetic characters, digits, 74 | and punctuation. 75 | """ 76 | string = "pecks.xlx\n" \ 77 | + "orders1.xls\n" \ 78 | + "apec1.xls\n" \ 79 | + "na1.xls\n" \ 80 | + "na2.xls\n" \ 81 | + "sa1.xls" 82 | 83 | # I want to find all uses of myArray 84 | change_this_search_string = 'a..xlx' 85 | self.assertEquals( 86 | len(re.findall(change_this_search_string, string)), 87 | 3) 88 | 89 | def test_matching_set_character(self): 90 | """ 91 | Lesson 2 -- Matching sets of characters 92 | 93 | A set of characters is defined using the metacharacters 94 | `[` and `]`. Everything between them is part of the set, and 95 | any single one of the set members will match. 96 | """ 97 | string = "sales.xlx\n" \ 98 | + "sales1.xls\n" \ 99 | + "orders3.xls\n" \ 100 | + "apac1.xls\n" \ 101 | + "sales2.xls\n" \ 102 | + "na1.xls\n" \ 103 | + "na2.xls\n" \ 104 | + "sa1.xls\n" \ 105 | + "ca1.xls" 106 | # I want to find all files for North America(na) or South 107 | # America(sa), but not (ca) TIP you can use the pattern .a. 108 | # which matches in above test but in this case matches more than 109 | # you want 110 | change_this_search_string = '[nsc]a[2-9].xls' 111 | self.assertEquals( 112 | len(re.findall(change_this_search_string, string)), 113 | 3) 114 | 115 | def test_anything_but_matching(self): 116 | """ 117 | Lesson 2 -- Using character set ranges 118 | Occasionally, you'll have a list of characters that you don't 119 | want to match. Character sets can be negated using the ^ 120 | metacharacter. 121 | 122 | """ 123 | string = "sales.xlx\n" \ 124 | + "sales1.xls\n" \ 125 | + "orders3.xls\n" \ 126 | + "apac1.xls\n" \ 127 | + "sales2.xls\n" \ 128 | + "sales3.xls\n" \ 129 | + "europe2.xls\n" \ 130 | + "sam.xls\n" \ 131 | + "na1.xls\n" \ 132 | + "na2.xls\n" \ 133 | + "sa1.xls\n" \ 134 | + "ca1.xls" 135 | 136 | # I want to find the name 'sam' 137 | change_this_search_string = '[^nc]am' 138 | self.assertEquals( 139 | re.findall(change_this_search_string, string), 140 | ['sam.xls']) 141 | -------------------------------------------------------------------------------- /koans/about_scope.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | from . import jims 7 | from . import joes 8 | 9 | counter = 0 # Global 10 | 11 | class AboutScope(Koan): 12 | # 13 | # NOTE: 14 | # Look in jims.py and joes.py to see definitions of Dog used 15 | # for this set of tests 16 | # 17 | 18 | def test_dog_is_not_available_in_the_current_scope(self): 19 | with self.assertRaises(___): fido = Dog() 20 | 21 | def test_you_can_reference_nested_classes_using_the_scope_operator(self): 22 | fido = jims.Dog() 23 | # name 'jims' module name is taken from jims.py filename 24 | 25 | rover = joes.Dog() 26 | self.assertEqual(__, fido.identify()) 27 | self.assertEqual(__, rover.identify()) 28 | 29 | self.assertEqual(__, type(fido) == type(rover)) 30 | self.assertEqual(__, jims.Dog == joes.Dog) 31 | 32 | # ------------------------------------------------------------------ 33 | 34 | class str: 35 | pass 36 | 37 | def test_bare_bones_class_names_do_not_assume_the_current_scope(self): 38 | self.assertEqual(__, AboutScope.str == str) 39 | 40 | def test_nested_string_is_not_the_same_as_the_system_string(self): 41 | self.assertEqual(__, self.str == type("HI")) 42 | 43 | def test_str_without_self_prefix_stays_in_the_global_scope(self): 44 | self.assertEqual(__, str == type("HI")) 45 | 46 | # ------------------------------------------------------------------ 47 | 48 | PI = 3.1416 49 | 50 | def test_constants_are_defined_with_an_initial_uppercase_letter(self): 51 | self.assertAlmostEqual(_____, self.PI) 52 | # Note, floating point numbers in python are not precise. 53 | # assertAlmostEqual will check that it is 'close enough' 54 | 55 | def test_constants_are_assumed_by_convention_only(self): 56 | self.PI = "rhubarb" 57 | self.assertEqual(_____, self.PI) 58 | # There aren't any real constants in python. Its up to the developer 59 | # to keep to the convention and not modify them. 60 | 61 | # ------------------------------------------------------------------ 62 | 63 | def increment_using_local_counter(self, counter): 64 | counter = counter + 1 65 | 66 | def increment_using_global_counter(self): 67 | global counter 68 | counter = counter + 1 69 | 70 | def test_incrementing_with_local_counter(self): 71 | global counter 72 | start = counter 73 | self.increment_using_local_counter(start) 74 | self.assertEqual(__, counter == start + 1) 75 | 76 | def test_incrementing_with_global_counter(self): 77 | global counter 78 | start = counter 79 | self.increment_using_global_counter() 80 | self.assertEqual(__, counter == start + 1) 81 | 82 | # ------------------------------------------------------------------ 83 | 84 | def local_access(self): 85 | stuff = 'eels' 86 | def from_the_league(): 87 | stuff = 'this is a local shop for local people' 88 | return stuff 89 | return from_the_league() 90 | 91 | def nonlocal_access(self): 92 | stuff = 'eels' 93 | def from_the_boosh(): 94 | nonlocal stuff 95 | return stuff 96 | return from_the_boosh() 97 | 98 | def test_getting_something_locally(self): 99 | self.assertEqual(__, self.local_access()) 100 | 101 | def test_getting_something_nonlocally(self): 102 | self.assertEqual(__, self.nonlocal_access()) 103 | 104 | # ------------------------------------------------------------------ 105 | 106 | global deadly_bingo 107 | deadly_bingo = [4, 8, 15, 16, 23, 42] 108 | 109 | def test_global_attributes_can_be_created_in_the_middle_of_a_class(self): 110 | self.assertEqual(__, deadly_bingo[5]) 111 | -------------------------------------------------------------------------------- /koans/about_scoring_project.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | # Greed is a dice game where you roll up to five dice to accumulate 7 | # points. The following "score" function will be used to calculate the 8 | # score of a single roll of the dice. 9 | # 10 | # A greed roll is scored as follows: 11 | # 12 | # * A set of three ones is 1000 points 13 | # 14 | # * A set of three numbers (other than ones) is worth 100 times the 15 | # number. (e.g. three fives is 500 points). 16 | # 17 | # * A one (that is not part of a set of three) is worth 100 points. 18 | # 19 | # * A five (that is not part of a set of three) is worth 50 points. 20 | # 21 | # * Everything else is worth 0 points. 22 | # 23 | # 24 | # Examples: 25 | # 26 | # score([1,1,1,5,1]) => 1150 points 27 | # score([2,3,4,6,2]) => 0 points 28 | # score([3,4,5,3,3]) => 350 points 29 | # score([1,5,1,2,4]) => 250 points 30 | # 31 | # More scoring examples are given in the tests below: 32 | # 33 | # Your goal is to write the score method. 34 | 35 | def score(dice): 36 | # You need to write this method 37 | pass 38 | 39 | class AboutScoringProject(Koan): 40 | def test_score_of_an_empty_list_is_zero(self): 41 | self.assertEqual(0, score([])) 42 | 43 | def test_score_of_a_single_roll_of_5_is_50(self): 44 | self.assertEqual(50, score([5])) 45 | 46 | def test_score_of_a_single_roll_of_1_is_100(self): 47 | self.assertEqual(100, score([1])) 48 | 49 | def test_score_of_multiple_1s_and_5s_is_the_sum_of_individual_scores(self): 50 | self.assertEqual(300, score([1,5,5,1])) 51 | 52 | def test_score_of_single_2s_3s_4s_and_6s_are_zero(self): 53 | self.assertEqual(0, score([2,3,4,6])) 54 | 55 | def test_score_of_a_triple_1_is_1000(self): 56 | self.assertEqual(1000, score([1,1,1])) 57 | 58 | def test_score_of_other_triples_is_100x(self): 59 | self.assertEqual(200, score([2,2,2])) 60 | self.assertEqual(300, score([3,3,3])) 61 | self.assertEqual(400, score([4,4,4])) 62 | self.assertEqual(500, score([5,5,5])) 63 | self.assertEqual(600, score([6,6,6])) 64 | 65 | def test_score_of_mixed_is_sum(self): 66 | self.assertEqual(250, score([2,5,2,2,3])) 67 | self.assertEqual(550, score([5,5,5,5])) 68 | self.assertEqual(1150, score([1,1,1,5,1])) 69 | 70 | def test_ones_not_left_out(self): 71 | self.assertEqual(300, score([1,2,2,2])) 72 | self.assertEqual(350, score([1,5,2,2,2])) 73 | -------------------------------------------------------------------------------- /koans/about_sets.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | class AboutSets(Koan): 7 | def test_sets_make_keep_lists_unique(self): 8 | highlanders = ['MacLeod', 'Ramirez', 'MacLeod', 'Matunas', 'MacLeod', 'Malcolm', 'MacLeod'] 9 | 10 | there_can_only_be_only_one = set(highlanders) 11 | 12 | self.assertEqual(__, there_can_only_be_only_one) 13 | 14 | def test_empty_sets_have_different_syntax_to_populated_sets(self): 15 | self.assertEqual(__, {1, 2, 3}) 16 | self.assertEqual(__, set()) 17 | 18 | def test_dictionaries_and_sets_use_same_curly_braces(self): 19 | # Note: Literal sets using braces were introduced in python 3. 20 | # They were also backported to python 2.7. 21 | 22 | self.assertEqual(__, {1, 2, 3}.__class__) 23 | self.assertEqual(__, {'one': 1, 'two': 2}.__class__) 24 | 25 | self.assertEqual(__, {}.__class__) 26 | 27 | def test_creating_sets_using_strings(self): 28 | self.assertEqual(__, {'12345'}) 29 | self.assertEqual(__, set('12345')) 30 | 31 | def test_convert_the_set_into_a_list_to_sort_it(self): 32 | self.assertEqual(__, sorted(set('12345'))) 33 | 34 | # ------------------------------------------------------------------ 35 | 36 | def test_set_have_arithmetic_operators(self): 37 | scotsmen = {'MacLeod', 'Wallace', 'Willie'} 38 | warriors = {'MacLeod', 'Wallace', 'Leonidas'} 39 | 40 | self.assertEqual(__, scotsmen - warriors) 41 | self.assertEqual(__, scotsmen | warriors) 42 | self.assertEqual(__, scotsmen & warriors) 43 | self.assertEqual(__, scotsmen ^ warriors) 44 | 45 | # ------------------------------------------------------------------ 46 | 47 | def test_we_can_query_set_membership(self): 48 | self.assertEqual(__, 127 in {127, 0, 0, 1} ) 49 | self.assertEqual(__, 'cow' not in set('apocalypse now') ) 50 | 51 | def test_we_can_compare_subsets(self): 52 | self.assertEqual(__, set('cake') <= set('cherry cake')) 53 | self.assertEqual(__, set('cake').issubset(set('cherry cake')) ) 54 | 55 | self.assertEqual(__, set('cake') > set('pie')) 56 | -------------------------------------------------------------------------------- /koans/about_string_manipulation.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | class AboutStringManipulation(Koan): 7 | 8 | def test_use_format_to_interpolate_variables(self): 9 | value1 = 'one' 10 | value2 = 2 11 | string = "The values are {0} and {1}".format(value1, value2) 12 | self.assertEqual(__, string) 13 | 14 | def test_formatted_values_can_be_shown_in_any_order_or_be_repeated(self): 15 | value1 = 'doh' 16 | value2 = 'DOH' 17 | string = "The values are {1}, {0}, {0} and {1}!".format(value1, value2) 18 | self.assertEqual(__, string) 19 | 20 | def test_any_python_expression_may_be_interpolated(self): 21 | import math # import a standard python module with math functions 22 | 23 | decimal_places = 4 24 | string = "The square root of 5 is {0:.{1}f}".format(math.sqrt(5), 25 | decimal_places) 26 | self.assertEqual(__, string) 27 | 28 | def test_you_can_get_a_substring_from_a_string(self): 29 | string = "Bacon, lettuce and tomato" 30 | self.assertEqual(__, string[7:10]) 31 | 32 | def test_you_can_get_a_single_character_from_a_string(self): 33 | string = "Bacon, lettuce and tomato" 34 | self.assertEqual(__, string[1]) 35 | 36 | def test_single_characters_can_be_represented_by_integers(self): 37 | self.assertEqual(__, ord('a')) 38 | self.assertEqual(__, ord('b') == (ord('a') + 1)) 39 | 40 | def test_strings_can_be_split(self): 41 | string = "Sausage Egg Cheese" 42 | words = string.split() 43 | self.assertListEqual([__, __, __], words) 44 | 45 | def test_strings_can_be_split_with_different_patterns(self): 46 | import re #import python regular expression library 47 | 48 | string = "the,rain;in,spain" 49 | pattern = re.compile(',|;') 50 | 51 | words = pattern.split(string) 52 | 53 | self.assertListEqual([__, __, __, __], words) 54 | 55 | # Pattern is a Python regular expression pattern which matches ',' or ';' 56 | 57 | def test_raw_strings_do_not_interpret_escape_characters(self): 58 | string = r'\n' 59 | self.assertNotEqual('\n', string) 60 | self.assertEqual(__, string) 61 | self.assertEqual(__, len(string)) 62 | 63 | # Useful in regular expressions, file paths, URLs, etc. 64 | 65 | def test_strings_can_be_joined(self): 66 | words = ["Now", "is", "the", "time"] 67 | self.assertEqual(__, ' '.join(words)) 68 | 69 | def test_strings_can_change_case(self): 70 | self.assertEqual(__, 'guido'.capitalize()) 71 | self.assertEqual(__, 'guido'.upper()) 72 | self.assertEqual(__, 'TimBot'.lower()) 73 | self.assertEqual(__, 'guido van rossum'.title()) 74 | self.assertEqual(__, 'ToTaLlY aWeSoMe'.swapcase()) 75 | -------------------------------------------------------------------------------- /koans/about_strings.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | class AboutStrings(Koan): 7 | 8 | def test_double_quoted_strings_are_strings(self): 9 | string = "Hello, world." 10 | self.assertEqual(__, isinstance(string, str)) 11 | 12 | def test_single_quoted_strings_are_also_strings(self): 13 | string = 'Goodbye, world.' 14 | self.assertEqual(__, isinstance(string, str)) 15 | 16 | def test_triple_quote_strings_are_also_strings(self): 17 | string = """Howdy, world!""" 18 | self.assertEqual(__, isinstance(string, str)) 19 | 20 | def test_triple_single_quotes_work_too(self): 21 | string = '''Bonjour tout le monde!''' 22 | self.assertEqual(__, isinstance(string, str)) 23 | 24 | def test_raw_strings_are_also_strings(self): 25 | string = r"Konnichi wa, world!" 26 | self.assertEqual(__, isinstance(string, str)) 27 | 28 | def test_use_single_quotes_to_create_string_with_double_quotes(self): 29 | string = 'He said, "Go Away."' 30 | self.assertEqual(__, string) 31 | 32 | def test_use_double_quotes_to_create_strings_with_single_quotes(self): 33 | string = "Don't" 34 | self.assertEqual(__, string) 35 | 36 | def test_use_backslash_for_escaping_quotes_in_strings(self): 37 | a = "He said, \"Don't\"" 38 | b = 'He said, "Don\'t"' 39 | self.assertEqual(__, (a == b)) 40 | 41 | def test_use_backslash_at_the_end_of_a_line_to_continue_onto_the_next_line(self): 42 | string = "It was the best of times,\n\ 43 | It was the worst of times." 44 | self.assertEqual(__, len(string)) 45 | 46 | def test_triple_quoted_strings_can_span_lines(self): 47 | string = """ 48 | Howdy, 49 | world! 50 | """ 51 | self.assertEqual(__, len(string)) 52 | 53 | def test_triple_quoted_strings_need_less_escaping(self): 54 | a = "Hello \"world\"." 55 | b = """Hello "world".""" 56 | self.assertEqual(__, (a == b)) 57 | 58 | def test_escaping_quotes_at_the_end_of_triple_quoted_string(self): 59 | string = """Hello "world\"""" 60 | self.assertEqual(__, string) 61 | 62 | def test_plus_concatenates_strings(self): 63 | string = "Hello, " + "world" 64 | self.assertEqual(__, string) 65 | 66 | def test_adjacent_strings_are_concatenated_automatically(self): 67 | string = "Hello" ", " "world" 68 | self.assertEqual(__, string) 69 | 70 | def test_plus_will_not_modify_original_strings(self): 71 | hi = "Hello, " 72 | there = "world" 73 | string = hi + there 74 | self.assertEqual(__, hi) 75 | self.assertEqual(__, there) 76 | 77 | def test_plus_equals_will_append_to_end_of_string(self): 78 | hi = "Hello, " 79 | there = "world" 80 | hi += there 81 | self.assertEqual(__, hi) 82 | 83 | def test_plus_equals_also_leaves_original_string_unmodified(self): 84 | original = "Hello, " 85 | hi = original 86 | there = "world" 87 | hi += there 88 | self.assertEqual(__, original) 89 | 90 | def test_most_strings_interpret_escape_characters(self): 91 | string = "\n" 92 | self.assertEqual('\n', string) 93 | self.assertEqual("""\n""", string) 94 | self.assertEqual(__, len(string)) 95 | -------------------------------------------------------------------------------- /koans/about_triangle_project.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | # You need to write the triangle method in the file 'triangle.py' 7 | from .triangle import * 8 | 9 | class AboutTriangleProject(Koan): 10 | def test_equilateral_triangles_have_equal_sides(self): 11 | self.assertEqual('equilateral', triangle(2, 2, 2)) 12 | self.assertEqual('equilateral', triangle(10, 10, 10)) 13 | 14 | def test_isosceles_triangles_have_exactly_two_sides_equal(self): 15 | self.assertEqual('isosceles', triangle(3, 4, 4)) 16 | self.assertEqual('isosceles', triangle(4, 3, 4)) 17 | self.assertEqual('isosceles', triangle(4, 4, 3)) 18 | self.assertEqual('isosceles', triangle(10, 10, 2)) 19 | 20 | def test_scalene_triangles_have_no_equal_sides(self): 21 | self.assertEqual('scalene', triangle(3, 4, 5)) 22 | self.assertEqual('scalene', triangle(10, 11, 12)) 23 | self.assertEqual('scalene', triangle(5, 4, 2)) 24 | -------------------------------------------------------------------------------- /koans/about_triangle_project2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | # You need to finish implementing triangle() in the file 'triangle.py' 7 | from .triangle import * 8 | 9 | class AboutTriangleProject2(Koan): 10 | # The first assignment did not talk about how to handle errors. 11 | # Let's handle that part now. 12 | def test_illegal_triangles_throw_exceptions(self): 13 | # All sides should be greater than 0 14 | with self.assertRaises(TriangleError): 15 | triangle(0, 0, 0) 16 | with self.assertRaises(TriangleError): 17 | triangle(3, 4, -5) 18 | 19 | # The sum of any two sides should be greater than the third one 20 | with self.assertRaises(TriangleError): 21 | triangle(1, 1, 3) 22 | with self.assertRaises(TriangleError): 23 | triangle(2, 5, 2) 24 | 25 | 26 | -------------------------------------------------------------------------------- /koans/about_true_and_false.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | 7 | class AboutTrueAndFalse(Koan): 8 | def truth_value(self, condition): 9 | if condition: 10 | return 'true stuff' 11 | else: 12 | return 'false stuff' 13 | 14 | def test_true_is_treated_as_true(self): 15 | self.assertEqual(__, self.truth_value(True)) 16 | 17 | def test_false_is_treated_as_false(self): 18 | self.assertEqual(__, self.truth_value(False)) 19 | 20 | def test_none_is_treated_as_false(self): 21 | self.assertEqual(__, self.truth_value(None)) 22 | 23 | def test_zero_is_treated_as_false(self): 24 | self.assertEqual(__, self.truth_value(0)) 25 | 26 | def test_empty_collections_are_treated_as_false(self): 27 | self.assertEqual(__, self.truth_value([])) 28 | self.assertEqual(__, self.truth_value(())) 29 | self.assertEqual(__, self.truth_value({})) 30 | self.assertEqual(__, self.truth_value(set())) 31 | 32 | def test_blank_strings_are_treated_as_false(self): 33 | self.assertEqual(__, self.truth_value("")) 34 | 35 | def test_everything_else_is_treated_as_true(self): 36 | self.assertEqual(__, self.truth_value(1)) 37 | self.assertEqual(__, self.truth_value([0])) 38 | self.assertEqual(__, self.truth_value((0,))) 39 | self.assertEqual( 40 | __, 41 | self.truth_value("Python is named after Monty Python")) 42 | self.assertEqual(__, self.truth_value(' ')) 43 | self.assertEqual(__, self.truth_value('0')) 44 | -------------------------------------------------------------------------------- /koans/about_tuples.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from runner.koan import * 5 | 6 | class AboutTuples(Koan): 7 | def test_creating_a_tuple(self): 8 | count_of_three = (1, 2, 5) 9 | self.assertEqual(__, count_of_three[2]) 10 | 11 | def test_tuples_are_immutable_so_item_assignment_is_not_possible(self): 12 | 13 | count_of_three = (1, 2, 5) 14 | try: 15 | count_of_three[2] = "three" 16 | except TypeError as ex: 17 | msg = ex.args[0] 18 | 19 | # Note, assertRegex() uses regular expression pattern matching, 20 | # so you don't have to copy the whole message. 21 | 22 | self.assertRegex(msg, __) 23 | 24 | def test_tuples_are_immutable_so_appending_is_not_possible(self): 25 | count_of_three = (1, 2, 5) 26 | with self.assertRaises(___): count_of_three.append("boom") 27 | 28 | # Tuples are less flexible than lists, but faster. 29 | 30 | def test_tuples_can_only_be_changed_through_replacement(self): 31 | count_of_three = (1, 2, 5) 32 | 33 | list_count = list(count_of_three) 34 | list_count.append("boom") 35 | count_of_three = tuple(list_count) 36 | 37 | self.assertEqual(__, count_of_three) 38 | 39 | def test_tuples_of_one_look_peculiar(self): 40 | self.assertEqual(__, (1).__class__) 41 | self.assertEqual(__, (1,).__class__) 42 | self.assertEqual(__, ("I'm a tuple",).__class__) 43 | self.assertEqual(__, ("Not a tuple").__class__) 44 | 45 | def test_tuple_constructor_can_be_surprising(self): 46 | self.assertEqual(__, tuple("Surprise!")) 47 | 48 | def test_creating_empty_tuples(self): 49 | self.assertEqual(__ , ()) 50 | self.assertEqual(__ , tuple()) # Sometimes less confusing 51 | 52 | def test_tuples_can_be_embedded(self): 53 | lat = (37, 14, 6, 'N') 54 | lon = (115, 48, 40, 'W') 55 | place = ('Area 51', lat, lon) 56 | self.assertEqual(__, place) 57 | 58 | def test_tuples_are_good_for_representing_records(self): 59 | locations = [ 60 | ("Illuminati HQ", (38, 52, 15.56, 'N'), (77, 3, 21.46, 'W')), 61 | ("Stargate B", (41, 10, 43.92, 'N'), (1, 49, 34.29, 'W')), 62 | ] 63 | 64 | locations.append( ("Cthulu", (26, 40, 1, 'N'), (70, 45, 7, 'W')) ) 65 | 66 | self.assertEqual(__, locations[2][0]) 67 | self.assertEqual(__, locations[0][1][2]) 68 | -------------------------------------------------------------------------------- /koans/about_with_statements.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # 5 | # Based on AboutSandwichCode in the Ruby Koans 6 | # 7 | 8 | from runner.koan import * 9 | 10 | import re # For regular expression string comparisons 11 | 12 | class AboutWithStatements(Koan): 13 | def count_lines(self, file_name): 14 | try: 15 | file = open(file_name) 16 | try: 17 | return len(file.readlines()) 18 | finally: 19 | file.close() 20 | except IOError: 21 | # should never happen 22 | self.fail() 23 | 24 | def test_counting_lines(self): 25 | self.assertEqual(__, self.count_lines("example_file.txt")) 26 | 27 | # ------------------------------------------------------------------ 28 | 29 | def find_line(self, file_name): 30 | try: 31 | file = open(file_name) 32 | try: 33 | for line in file.readlines(): 34 | match = re.search('e', line) 35 | if match: 36 | return line 37 | finally: 38 | file.close() 39 | except IOError: 40 | # should never happen 41 | self.fail() 42 | 43 | def test_finding_lines(self): 44 | self.assertEqual(__, self.find_line("example_file.txt")) 45 | 46 | ## ------------------------------------------------------------------ 47 | ## THINK ABOUT IT: 48 | ## 49 | ## The count_lines and find_line are similar, and yet different. 50 | ## They both follow the pattern of "sandwich code". 51 | ## 52 | ## Sandwich code is code that comes in three parts: (1) the top slice 53 | ## of bread, (2) the meat, and (3) the bottom slice of bread. 54 | ## The bread part of the sandwich almost always goes together, but 55 | ## the meat part changes all the time. 56 | ## 57 | ## Because the changing part of the sandwich code is in the middle, 58 | ## abstracting the top and bottom bread slices to a library can be 59 | ## difficult in many languages. 60 | ## 61 | ## (Aside for C++ programmers: The idiom of capturing allocated 62 | ## pointers in a smart pointer constructor is an attempt to deal with 63 | ## the problem of sandwich code for resource allocation.) 64 | ## 65 | ## Python solves the problem using Context Managers. Consider the 66 | ## following code: 67 | ## 68 | 69 | class FileContextManager(): 70 | def __init__(self, file_name): 71 | self._file_name = file_name 72 | self._file = None 73 | 74 | def __enter__(self): 75 | self._file = open(self._file_name) 76 | return self._file 77 | 78 | def __exit__(self, cls, value, tb): 79 | self._file.close() 80 | 81 | # Now we write: 82 | 83 | def count_lines2(self, file_name): 84 | with self.FileContextManager(file_name) as file: 85 | return len(file.readlines()) 86 | 87 | def test_counting_lines2(self): 88 | self.assertEqual(__, self.count_lines2("example_file.txt")) 89 | 90 | # ------------------------------------------------------------------ 91 | 92 | def find_line2(self, file_name): 93 | # Using the context manager self.FileContextManager, rewrite this 94 | # function to return the first line containing the letter 'e'. 95 | return None 96 | 97 | def test_finding_lines2(self): 98 | self.assertNotEqual(None, self.find_line2("example_file.txt")) 99 | self.assertEqual('test\n', self.find_line2("example_file.txt")) 100 | 101 | # ------------------------------------------------------------------ 102 | 103 | def count_lines3(self, file_name): 104 | with open(file_name) as file: 105 | return len(file.readlines()) 106 | 107 | def test_open_already_has_its_own_built_in_context_manager(self): 108 | self.assertEqual(__, self.count_lines3("example_file.txt")) 109 | -------------------------------------------------------------------------------- /koans/another_local_module.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | class Goose: 5 | @property 6 | def name(self): 7 | return "Mr Stabby" 8 | 9 | class Hamster: 10 | @property 11 | def name(self): 12 | return "Phil" 13 | 14 | class _SecretSquirrel: 15 | @property 16 | def name(self): 17 | return "Mr Anonymous" -------------------------------------------------------------------------------- /koans/jims.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | class Dog: 5 | def identify(self): 6 | return "jims dog" 7 | -------------------------------------------------------------------------------- /koans/joes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | class Dog: 5 | def identify(self): 6 | return "joes dog" 7 | -------------------------------------------------------------------------------- /koans/local_module.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | class Duck: 5 | def __init__(self): 6 | self._password = 'password' # Genius! 7 | 8 | @property 9 | def name(self): 10 | return "Daffy" 11 | -------------------------------------------------------------------------------- /koans/local_module_with_all_defined.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | __all__ = ( 5 | 'Goat', 6 | '_Velociraptor' 7 | ) 8 | 9 | class Goat: 10 | @property 11 | def name(self): 12 | return "George" 13 | 14 | class _Velociraptor: 15 | @property 16 | def name(self): 17 | return "Cuddles" 18 | 19 | class SecretDuck: 20 | @property 21 | def name(self): 22 | return "None of your business" 23 | -------------------------------------------------------------------------------- /koans/triangle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Triangle Project Code. 5 | 6 | # Triangle analyzes the lengths of the sides of a triangle 7 | # (represented by a, b and c) and returns the type of triangle. 8 | # 9 | # It returns: 10 | # 'equilateral' if all sides are equal 11 | # 'isosceles' if exactly 2 sides are equal 12 | # 'scalene' if no sides are equal 13 | # 14 | # The tests for this method can be found in 15 | # about_triangle_project.py 16 | # and 17 | # about_triangle_project_2.py 18 | # 19 | def triangle(a, b, c): 20 | # DELETE 'PASS' AND WRITE THIS CODE 21 | pass 22 | 23 | # Error class used in part 2. No need to change this code. 24 | class TriangleError(Exception): 25 | pass 26 | -------------------------------------------------------------------------------- /libs/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Dummy file to support python package hierarchy 5 | -------------------------------------------------------------------------------- /libs/colorama/LICENSE-colorama: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Jonathan Hartley 2 | 3 | Released under the New BSD license (reproduced below), or alternatively you may 4 | use this software under any OSI approved open source license such as those at 5 | http://opensource.org/licenses/alphabetical 6 | 7 | All rights reserved. 8 | 9 | Redistribution and use in source and binary forms, with or without 10 | modification, are permitted provided that the following conditions are met: 11 | 12 | * Redistributions of source code must retain the above copyright notice, this 13 | list of conditions and the following disclaimer. 14 | 15 | * Redistributions in binary form must reproduce the above copyright notice, 16 | this list of conditions and the following disclaimer in the documentation 17 | and/or other materials provided with the distribution. 18 | 19 | * Neither the name(s) of the copyright holders, nor those of its contributors 20 | may be used to endorse or promote products derived from this software without 21 | specific prior written permission. 22 | 23 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 24 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 25 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 26 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 27 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 29 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 30 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 31 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 32 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | 34 | -------------------------------------------------------------------------------- /libs/colorama/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. 2 | from .initialise import init, deinit, reinit 3 | from .ansi import Fore, Back, Style 4 | from .ansitowin32 import AnsiToWin32 5 | 6 | VERSION = '0.2.7' 7 | 8 | -------------------------------------------------------------------------------- /libs/colorama/ansi.py: -------------------------------------------------------------------------------- 1 | # Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. 2 | ''' 3 | This module generates ANSI character codes to printing colors to terminals. 4 | See: http://en.wikipedia.org/wiki/ANSI_escape_code 5 | ''' 6 | 7 | CSI = '\033[' 8 | 9 | def code_to_chars(code): 10 | return CSI + str(code) + 'm' 11 | 12 | class AnsiCodes(object): 13 | def __init__(self, codes): 14 | for name in dir(codes): 15 | if not name.startswith('_'): 16 | value = getattr(codes, name) 17 | setattr(self, name, code_to_chars(value)) 18 | 19 | class AnsiFore: 20 | BLACK = 30 21 | RED = 31 22 | GREEN = 32 23 | YELLOW = 33 24 | BLUE = 34 25 | MAGENTA = 35 26 | CYAN = 36 27 | WHITE = 37 28 | RESET = 39 29 | 30 | class AnsiBack: 31 | BLACK = 40 32 | RED = 41 33 | GREEN = 42 34 | YELLOW = 43 35 | BLUE = 44 36 | MAGENTA = 45 37 | CYAN = 46 38 | WHITE = 47 39 | RESET = 49 40 | 41 | class AnsiStyle: 42 | BRIGHT = 1 43 | DIM = 2 44 | NORMAL = 22 45 | RESET_ALL = 0 46 | 47 | Fore = AnsiCodes( AnsiFore ) 48 | Back = AnsiCodes( AnsiBack ) 49 | Style = AnsiCodes( AnsiStyle ) 50 | 51 | -------------------------------------------------------------------------------- /libs/colorama/ansitowin32.py: -------------------------------------------------------------------------------- 1 | # Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. 2 | import re 3 | import sys 4 | 5 | from .ansi import AnsiFore, AnsiBack, AnsiStyle, Style 6 | from .winterm import WinTerm, WinColor, WinStyle 7 | from .win32 import windll 8 | 9 | 10 | if windll is not None: 11 | winterm = WinTerm() 12 | 13 | 14 | def is_a_tty(stream): 15 | return hasattr(stream, 'isatty') and stream.isatty() 16 | 17 | 18 | class StreamWrapper(object): 19 | ''' 20 | Wraps a stream (such as stdout), acting as a transparent proxy for all 21 | attribute access apart from method 'write()', which is delegated to our 22 | Converter instance. 23 | ''' 24 | def __init__(self, wrapped, converter): 25 | # double-underscore everything to prevent clashes with names of 26 | # attributes on the wrapped stream object. 27 | self.__wrapped = wrapped 28 | self.__convertor = converter 29 | 30 | def __getattr__(self, name): 31 | return getattr(self.__wrapped, name) 32 | 33 | def write(self, text): 34 | self.__convertor.write(text) 35 | 36 | 37 | class AnsiToWin32(object): 38 | ''' 39 | Implements a 'write()' method which, on Windows, will strip ANSI character 40 | sequences from the text, and if outputting to a tty, will convert them into 41 | win32 function calls. 42 | ''' 43 | ANSI_RE = re.compile('\033\[((?:\d|;)*)([a-zA-Z])') 44 | 45 | def __init__(self, wrapped, convert=None, strip=None, autoreset=False): 46 | # The wrapped stream (normally sys.stdout or sys.stderr) 47 | self.wrapped = wrapped 48 | 49 | # should we reset colors to defaults after every .write() 50 | self.autoreset = autoreset 51 | 52 | # create the proxy wrapping our output stream 53 | self.stream = StreamWrapper(wrapped, self) 54 | 55 | on_windows = sys.platform.startswith('win') 56 | 57 | # should we strip ANSI sequences from our output? 58 | if strip is None: 59 | strip = on_windows 60 | self.strip = strip 61 | 62 | # should we should convert ANSI sequences into win32 calls? 63 | if convert is None: 64 | convert = on_windows and is_a_tty(wrapped) 65 | self.convert = convert 66 | 67 | # dict of ansi codes to win32 functions and parameters 68 | self.win32_calls = self.get_win32_calls() 69 | 70 | # are we wrapping stderr? 71 | self.on_stderr = self.wrapped is sys.stderr 72 | 73 | 74 | def should_wrap(self): 75 | ''' 76 | True if this class is actually needed. If false, then the output 77 | stream will not be affected, nor will win32 calls be issued, so 78 | wrapping stdout is not actually required. This will generally be 79 | False on non-Windows platforms, unless optional functionality like 80 | autoreset has been requested using kwargs to init() 81 | ''' 82 | return self.convert or self.strip or self.autoreset 83 | 84 | 85 | def get_win32_calls(self): 86 | if self.convert and winterm: 87 | return { 88 | AnsiStyle.RESET_ALL: (winterm.reset_all, ), 89 | AnsiStyle.BRIGHT: (winterm.style, WinStyle.BRIGHT), 90 | AnsiStyle.DIM: (winterm.style, WinStyle.NORMAL), 91 | AnsiStyle.NORMAL: (winterm.style, WinStyle.NORMAL), 92 | AnsiFore.BLACK: (winterm.fore, WinColor.BLACK), 93 | AnsiFore.RED: (winterm.fore, WinColor.RED), 94 | AnsiFore.GREEN: (winterm.fore, WinColor.GREEN), 95 | AnsiFore.YELLOW: (winterm.fore, WinColor.YELLOW), 96 | AnsiFore.BLUE: (winterm.fore, WinColor.BLUE), 97 | AnsiFore.MAGENTA: (winterm.fore, WinColor.MAGENTA), 98 | AnsiFore.CYAN: (winterm.fore, WinColor.CYAN), 99 | AnsiFore.WHITE: (winterm.fore, WinColor.GREY), 100 | AnsiFore.RESET: (winterm.fore, ), 101 | AnsiBack.BLACK: (winterm.back, WinColor.BLACK), 102 | AnsiBack.RED: (winterm.back, WinColor.RED), 103 | AnsiBack.GREEN: (winterm.back, WinColor.GREEN), 104 | AnsiBack.YELLOW: (winterm.back, WinColor.YELLOW), 105 | AnsiBack.BLUE: (winterm.back, WinColor.BLUE), 106 | AnsiBack.MAGENTA: (winterm.back, WinColor.MAGENTA), 107 | AnsiBack.CYAN: (winterm.back, WinColor.CYAN), 108 | AnsiBack.WHITE: (winterm.back, WinColor.GREY), 109 | AnsiBack.RESET: (winterm.back, ), 110 | } 111 | 112 | 113 | def write(self, text): 114 | if self.strip or self.convert: 115 | self.write_and_convert(text) 116 | else: 117 | self.wrapped.write(text) 118 | self.wrapped.flush() 119 | if self.autoreset: 120 | self.reset_all() 121 | 122 | 123 | def reset_all(self): 124 | if self.convert: 125 | self.call_win32('m', (0,)) 126 | elif is_a_tty(self.wrapped): 127 | self.wrapped.write(Style.RESET_ALL) 128 | 129 | 130 | def write_and_convert(self, text): 131 | ''' 132 | Write the given text to our wrapped stream, stripping any ANSI 133 | sequences from the text, and optionally converting them into win32 134 | calls. 135 | ''' 136 | cursor = 0 137 | for match in self.ANSI_RE.finditer(text): 138 | start, end = match.span() 139 | self.write_plain_text(text, cursor, start) 140 | self.convert_ansi(*match.groups()) 141 | cursor = end 142 | self.write_plain_text(text, cursor, len(text)) 143 | 144 | 145 | def write_plain_text(self, text, start, end): 146 | if start < end: 147 | self.wrapped.write(text[start:end]) 148 | self.wrapped.flush() 149 | 150 | 151 | def convert_ansi(self, paramstring, command): 152 | if self.convert: 153 | params = self.extract_params(paramstring) 154 | self.call_win32(command, params) 155 | 156 | 157 | def extract_params(self, paramstring): 158 | def split(paramstring): 159 | for p in paramstring.split(';'): 160 | if p != '': 161 | yield int(p) 162 | return tuple(split(paramstring)) 163 | 164 | 165 | def call_win32(self, command, params): 166 | if params == []: 167 | params = [0] 168 | if command == 'm': 169 | for param in params: 170 | if param in self.win32_calls: 171 | func_args = self.win32_calls[param] 172 | func = func_args[0] 173 | args = func_args[1:] 174 | kwargs = dict(on_stderr=self.on_stderr) 175 | func(*args, **kwargs) 176 | elif command in ('H', 'f'): # set cursor position 177 | func = winterm.set_cursor_position 178 | func(params, on_stderr=self.on_stderr) 179 | elif command in ('J'): 180 | func = winterm.erase_data 181 | func(params, on_stderr=self.on_stderr) 182 | elif command == 'A': 183 | if params == () or params == None: 184 | num_rows = 1 185 | else: 186 | num_rows = params[0] 187 | func = winterm.cursor_up 188 | func(num_rows, on_stderr=self.on_stderr) 189 | 190 | -------------------------------------------------------------------------------- /libs/colorama/initialise.py: -------------------------------------------------------------------------------- 1 | # Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. 2 | import atexit 3 | import sys 4 | 5 | from .ansitowin32 import AnsiToWin32 6 | 7 | 8 | orig_stdout = sys.stdout 9 | orig_stderr = sys.stderr 10 | 11 | wrapped_stdout = sys.stdout 12 | wrapped_stderr = sys.stderr 13 | 14 | atexit_done = False 15 | 16 | 17 | def reset_all(): 18 | AnsiToWin32(orig_stdout).reset_all() 19 | 20 | 21 | def init(autoreset=False, convert=None, strip=None, wrap=True): 22 | 23 | if not wrap and any([autoreset, convert, strip]): 24 | raise ValueError('wrap=False conflicts with any other arg=True') 25 | 26 | global wrapped_stdout, wrapped_stderr 27 | sys.stdout = wrapped_stdout = \ 28 | wrap_stream(orig_stdout, convert, strip, autoreset, wrap) 29 | sys.stderr = wrapped_stderr = \ 30 | wrap_stream(orig_stderr, convert, strip, autoreset, wrap) 31 | 32 | global atexit_done 33 | if not atexit_done: 34 | atexit.register(reset_all) 35 | atexit_done = True 36 | 37 | 38 | def deinit(): 39 | sys.stdout = orig_stdout 40 | sys.stderr = orig_stderr 41 | 42 | 43 | def reinit(): 44 | sys.stdout = wrapped_stdout 45 | sys.stderr = wrapped_stdout 46 | 47 | 48 | def wrap_stream(stream, convert, strip, autoreset, wrap): 49 | if wrap: 50 | wrapper = AnsiToWin32(stream, 51 | convert=convert, strip=strip, autoreset=autoreset) 52 | if wrapper.should_wrap(): 53 | stream = wrapper.stream 54 | return stream 55 | 56 | 57 | -------------------------------------------------------------------------------- /libs/colorama/win32.py: -------------------------------------------------------------------------------- 1 | # Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. 2 | 3 | # from winbase.h 4 | STDOUT = -11 5 | STDERR = -12 6 | 7 | try: 8 | from ctypes import windll 9 | from ctypes import wintypes 10 | except ImportError: 11 | windll = None 12 | SetConsoleTextAttribute = lambda *_: None 13 | else: 14 | from ctypes import ( 15 | byref, Structure, c_char, c_short, c_uint32, c_ushort, POINTER 16 | ) 17 | 18 | class CONSOLE_SCREEN_BUFFER_INFO(Structure): 19 | """struct in wincon.h.""" 20 | _fields_ = [ 21 | ("dwSize", wintypes._COORD), 22 | ("dwCursorPosition", wintypes._COORD), 23 | ("wAttributes", wintypes.WORD), 24 | ("srWindow", wintypes.SMALL_RECT), 25 | ("dwMaximumWindowSize", wintypes._COORD), 26 | ] 27 | def __str__(self): 28 | return '(%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d)' % ( 29 | self.dwSize.Y, self.dwSize.X 30 | , self.dwCursorPosition.Y, self.dwCursorPosition.X 31 | , self.wAttributes 32 | , self.srWindow.Top, self.srWindow.Left, self.srWindow.Bottom, self.srWindow.Right 33 | , self.dwMaximumWindowSize.Y, self.dwMaximumWindowSize.X 34 | ) 35 | 36 | _GetStdHandle = windll.kernel32.GetStdHandle 37 | _GetStdHandle.argtypes = [ 38 | wintypes.DWORD, 39 | ] 40 | _GetStdHandle.restype = wintypes.HANDLE 41 | 42 | _GetConsoleScreenBufferInfo = windll.kernel32.GetConsoleScreenBufferInfo 43 | _GetConsoleScreenBufferInfo.argtypes = [ 44 | wintypes.HANDLE, 45 | POINTER(CONSOLE_SCREEN_BUFFER_INFO), 46 | ] 47 | _GetConsoleScreenBufferInfo.restype = wintypes.BOOL 48 | 49 | _SetConsoleTextAttribute = windll.kernel32.SetConsoleTextAttribute 50 | _SetConsoleTextAttribute.argtypes = [ 51 | wintypes.HANDLE, 52 | wintypes.WORD, 53 | ] 54 | _SetConsoleTextAttribute.restype = wintypes.BOOL 55 | 56 | _SetConsoleCursorPosition = windll.kernel32.SetConsoleCursorPosition 57 | _SetConsoleCursorPosition.argtypes = [ 58 | wintypes.HANDLE, 59 | wintypes._COORD, 60 | ] 61 | _SetConsoleCursorPosition.restype = wintypes.BOOL 62 | 63 | _FillConsoleOutputCharacterA = windll.kernel32.FillConsoleOutputCharacterA 64 | _FillConsoleOutputCharacterA.argtypes = [ 65 | wintypes.HANDLE, 66 | c_char, 67 | wintypes.DWORD, 68 | wintypes._COORD, 69 | POINTER(wintypes.DWORD), 70 | ] 71 | _FillConsoleOutputCharacterA.restype = wintypes.BOOL 72 | 73 | _FillConsoleOutputAttribute = windll.kernel32.FillConsoleOutputAttribute 74 | _FillConsoleOutputAttribute.argtypes = [ 75 | wintypes.HANDLE, 76 | wintypes.WORD, 77 | wintypes.DWORD, 78 | wintypes._COORD, 79 | POINTER(wintypes.DWORD), 80 | ] 81 | _FillConsoleOutputAttribute.restype = wintypes.BOOL 82 | 83 | handles = { 84 | STDOUT: _GetStdHandle(STDOUT), 85 | STDERR: _GetStdHandle(STDERR), 86 | } 87 | 88 | def GetConsoleScreenBufferInfo(stream_id=STDOUT): 89 | handle = handles[stream_id] 90 | csbi = CONSOLE_SCREEN_BUFFER_INFO() 91 | success = _GetConsoleScreenBufferInfo( 92 | handle, byref(csbi)) 93 | return csbi 94 | 95 | def SetConsoleTextAttribute(stream_id, attrs): 96 | handle = handles[stream_id] 97 | return _SetConsoleTextAttribute(handle, attrs) 98 | 99 | def SetConsoleCursorPosition(stream_id, position): 100 | position = wintypes._COORD(*position) 101 | # If the position is out of range, do nothing. 102 | if position.Y <= 0 or position.X <= 0: 103 | return 104 | # Adjust for Windows' SetConsoleCursorPosition: 105 | # 1. being 0-based, while ANSI is 1-based. 106 | # 2. expecting (x,y), while ANSI uses (y,x). 107 | adjusted_position = wintypes._COORD(position.Y - 1, position.X - 1) 108 | # Adjust for viewport's scroll position 109 | sr = GetConsoleScreenBufferInfo(STDOUT).srWindow 110 | adjusted_position.Y += sr.Top 111 | adjusted_position.X += sr.Left 112 | # Resume normal processing 113 | handle = handles[stream_id] 114 | return _SetConsoleCursorPosition(handle, adjusted_position) 115 | 116 | def FillConsoleOutputCharacter(stream_id, char, length, start): 117 | handle = handles[stream_id] 118 | char = c_char(char) 119 | length = wintypes.DWORD(length) 120 | num_written = wintypes.DWORD(0) 121 | # Note that this is hard-coded for ANSI (vs wide) bytes. 122 | success = _FillConsoleOutputCharacterA( 123 | handle, char, length, start, byref(num_written)) 124 | return num_written.value 125 | 126 | def FillConsoleOutputAttribute(stream_id, attr, length, start): 127 | ''' FillConsoleOutputAttribute( hConsole, csbi.wAttributes, dwConSize, coordScreen, &cCharsWritten )''' 128 | handle = handles[stream_id] 129 | attribute = wintypes.WORD(attr) 130 | length = wintypes.DWORD(length) 131 | num_written = wintypes.DWORD(0) 132 | # Note that this is hard-coded for ANSI (vs wide) bytes. 133 | return _FillConsoleOutputAttribute( 134 | handle, attribute, length, start, byref(num_written)) 135 | -------------------------------------------------------------------------------- /libs/colorama/winterm.py: -------------------------------------------------------------------------------- 1 | # Copyright Jonathan Hartley 2013. BSD 3-Clause license, see LICENSE file. 2 | from . import win32 3 | 4 | 5 | # from wincon.h 6 | class WinColor(object): 7 | BLACK = 0 8 | BLUE = 1 9 | GREEN = 2 10 | CYAN = 3 11 | RED = 4 12 | MAGENTA = 5 13 | YELLOW = 6 14 | GREY = 7 15 | 16 | # from wincon.h 17 | class WinStyle(object): 18 | NORMAL = 0x00 # dim text, dim background 19 | BRIGHT = 0x08 # bright text, dim background 20 | 21 | 22 | class WinTerm(object): 23 | 24 | def __init__(self): 25 | self._default = win32.GetConsoleScreenBufferInfo(win32.STDOUT).wAttributes 26 | self.set_attrs(self._default) 27 | self._default_fore = self._fore 28 | self._default_back = self._back 29 | self._default_style = self._style 30 | 31 | def get_attrs(self): 32 | return self._fore + self._back * 16 + self._style 33 | 34 | def set_attrs(self, value): 35 | self._fore = value & 7 36 | self._back = (value >> 4) & 7 37 | self._style = value & WinStyle.BRIGHT 38 | 39 | def reset_all(self, on_stderr=None): 40 | self.set_attrs(self._default) 41 | self.set_console(attrs=self._default) 42 | 43 | def fore(self, fore=None, on_stderr=False): 44 | if fore is None: 45 | fore = self._default_fore 46 | self._fore = fore 47 | self.set_console(on_stderr=on_stderr) 48 | 49 | def back(self, back=None, on_stderr=False): 50 | if back is None: 51 | back = self._default_back 52 | self._back = back 53 | self.set_console(on_stderr=on_stderr) 54 | 55 | def style(self, style=None, on_stderr=False): 56 | if style is None: 57 | style = self._default_style 58 | self._style = style 59 | self.set_console(on_stderr=on_stderr) 60 | 61 | def set_console(self, attrs=None, on_stderr=False): 62 | if attrs is None: 63 | attrs = self.get_attrs() 64 | handle = win32.STDOUT 65 | if on_stderr: 66 | handle = win32.STDERR 67 | win32.SetConsoleTextAttribute(handle, attrs) 68 | 69 | def get_position(self, handle): 70 | position = win32.GetConsoleScreenBufferInfo(handle).dwCursorPosition 71 | # Because Windows coordinates are 0-based, 72 | # and win32.SetConsoleCursorPosition expects 1-based. 73 | position.X += 1 74 | position.Y += 1 75 | return position 76 | 77 | def set_cursor_position(self, position=None, on_stderr=False): 78 | if position is None: 79 | #I'm not currently tracking the position, so there is no default. 80 | #position = self.get_position() 81 | return 82 | handle = win32.STDOUT 83 | if on_stderr: 84 | handle = win32.STDERR 85 | win32.SetConsoleCursorPosition(handle, position) 86 | 87 | def cursor_up(self, num_rows=0, on_stderr=False): 88 | if num_rows == 0: 89 | return 90 | handle = win32.STDOUT 91 | if on_stderr: 92 | handle = win32.STDERR 93 | position = self.get_position(handle) 94 | adjusted_position = (position.Y - num_rows, position.X) 95 | self.set_cursor_position(adjusted_position, on_stderr) 96 | 97 | def erase_data(self, mode=0, on_stderr=False): 98 | # 0 (or None) should clear from the cursor to the end of the screen. 99 | # 1 should clear from the cursor to the beginning of the screen. 100 | # 2 should clear the entire screen. (And maybe move cursor to (1,1)?) 101 | # 102 | # At the moment, I only support mode 2. From looking at the API, it 103 | # should be possible to calculate a different number of bytes to clear, 104 | # and to do so relative to the cursor position. 105 | if mode[0] not in (2,): 106 | return 107 | handle = win32.STDOUT 108 | if on_stderr: 109 | handle = win32.STDERR 110 | # here's where we'll home the cursor 111 | coord_screen = win32.COORD(0,0) 112 | csbi = win32.GetConsoleScreenBufferInfo(handle) 113 | # get the number of character cells in the current buffer 114 | dw_con_size = csbi.dwSize.X * csbi.dwSize.Y 115 | # fill the entire screen with blanks 116 | win32.FillConsoleOutputCharacter(handle, ' ', dw_con_size, coord_screen) 117 | # now set the buffer's attributes accordingly 118 | win32.FillConsoleOutputAttribute(handle, self.get_attrs(), dw_con_size, coord_screen ); 119 | # put the cursor at (0, 0) 120 | win32.SetConsoleCursorPosition(handle, (coord_screen.X, coord_screen.Y)) 121 | -------------------------------------------------------------------------------- /libs/mock.py: -------------------------------------------------------------------------------- 1 | # mock.py 2 | # Test tools for mocking and patching. 3 | # Copyright (C) 2007-2009 Michael Foord 4 | # E-mail: fuzzyman AT voidspace DOT org DOT uk 5 | 6 | # mock 0.6.0 7 | # http://www.voidspace.org.uk/python/mock/ 8 | 9 | # Released subject to the BSD License 10 | # Please see http://www.voidspace.org.uk/python/license.shtml 11 | 12 | # Scripts maintained at http://www.voidspace.org.uk/python/index.shtml 13 | # Comments, suggestions and bug reports welcome. 14 | 15 | 16 | __all__ = ( 17 | 'Mock', 18 | 'patch', 19 | 'patch_object', 20 | 'sentinel', 21 | 'DEFAULT' 22 | ) 23 | 24 | __version__ = '0.6.0 modified by Greg Malcolm' 25 | 26 | class SentinelObject(object): 27 | def __init__(self, name): 28 | self.name = name 29 | 30 | def __repr__(self): 31 | return ''.format(self.name) 32 | 33 | 34 | class Sentinel(object): 35 | def __init__(self): 36 | self._sentinels = {} 37 | 38 | def __getattr__(self, name): 39 | return self._sentinels.setdefault(name, SentinelObject(name)) 40 | 41 | 42 | sentinel = Sentinel() 43 | 44 | DEFAULT = sentinel.DEFAULT 45 | 46 | class OldStyleClass: 47 | pass 48 | ClassType = type(OldStyleClass) 49 | 50 | def _is_magic(name): 51 | return '__{0!s}__'.format(name[2:-2]) == name 52 | 53 | def _copy(value): 54 | if type(value) in (dict, list, tuple, set): 55 | return type(value)(value) 56 | return value 57 | 58 | 59 | class Mock(object): 60 | 61 | def __init__(self, spec=None, side_effect=None, return_value=DEFAULT, 62 | name=None, parent=None, wraps=None): 63 | self._parent = parent 64 | self._name = name 65 | if spec is not None and not isinstance(spec, list): 66 | spec = [member for member in dir(spec) if not _is_magic(member)] 67 | 68 | self._methods = spec 69 | self._children = {} 70 | self._return_value = return_value 71 | self.side_effect = side_effect 72 | self._wraps = wraps 73 | 74 | self.reset_mock() 75 | 76 | 77 | def reset_mock(self): 78 | self.called = False 79 | self.call_args = None 80 | self.call_count = 0 81 | self.call_args_list = [] 82 | self.method_calls = [] 83 | for child in self._children.values(): 84 | child.reset_mock() 85 | if isinstance(self._return_value, Mock): 86 | self._return_value.reset_mock() 87 | 88 | 89 | def __get_return_value(self): 90 | if self._return_value is DEFAULT: 91 | self._return_value = Mock() 92 | return self._return_value 93 | 94 | def __set_return_value(self, value): 95 | self._return_value = value 96 | 97 | return_value = property(__get_return_value, __set_return_value) 98 | 99 | 100 | def __call__(self, *args, **kwargs): 101 | self.called = True 102 | self.call_count += 1 103 | self.call_args = (args, kwargs) 104 | self.call_args_list.append((args, kwargs)) 105 | 106 | parent = self._parent 107 | name = self._name 108 | while parent is not None: 109 | parent.method_calls.append((name, args, kwargs)) 110 | if parent._parent is None: 111 | break 112 | name = parent._name + '.' + name 113 | parent = parent._parent 114 | 115 | ret_val = DEFAULT 116 | if self.side_effect is not None: 117 | if (isinstance(self.side_effect, Exception) or 118 | isinstance(self.side_effect, (type, ClassType)) and 119 | issubclass(self.side_effect, Exception)): 120 | raise self.side_effect 121 | 122 | ret_val = self.side_effect(*args, **kwargs) 123 | if ret_val is DEFAULT: 124 | ret_val = self.return_value 125 | 126 | if self._wraps is not None and self._return_value is DEFAULT: 127 | return self._wraps(*args, **kwargs) 128 | if ret_val is DEFAULT: 129 | ret_val = self.return_value 130 | return ret_val 131 | 132 | 133 | def __getattr__(self, name): 134 | if self._methods is not None: 135 | if name not in self._methods: 136 | raise AttributeError("Mock object has no attribute '{0!s}'".format(name)) 137 | elif _is_magic(name): 138 | raise AttributeError(name) 139 | 140 | if name not in self._children: 141 | wraps = None 142 | if self._wraps is not None: 143 | wraps = getattr(self._wraps, name) 144 | self._children[name] = Mock(parent=self, name=name, wraps=wraps) 145 | 146 | return self._children[name] 147 | 148 | 149 | def assert_called_with(self, *args, **kwargs): 150 | assert self.call_args == (args, kwargs), 'Expected: {0!s}\nCalled with: {1!s}'.format((args, kwargs), self.call_args) 151 | 152 | 153 | def _dot_lookup(thing, comp, import_path): 154 | try: 155 | return getattr(thing, comp) 156 | except AttributeError: 157 | __import__(import_path) 158 | return getattr(thing, comp) 159 | 160 | 161 | def _importer(target): 162 | components = target.split('.') 163 | import_path = components.pop(0) 164 | thing = __import__(import_path) 165 | 166 | for comp in components: 167 | import_path += ".{0!s}".format(comp) 168 | thing = _dot_lookup(thing, comp, import_path) 169 | return thing 170 | 171 | 172 | class _patch(object): 173 | def __init__(self, target, attribute, new, spec, create): 174 | self.target = target 175 | self.attribute = attribute 176 | self.new = new 177 | self.spec = spec 178 | self.create = create 179 | self.has_local = False 180 | 181 | 182 | def __call__(self, func): 183 | if hasattr(func, 'patchings'): 184 | func.patchings.append(self) 185 | return func 186 | 187 | def patched(*args, **keywargs): 188 | # don't use a with here (backwards compatability with 2.5) 189 | extra_args = [] 190 | for patching in patched.patchings: 191 | arg = patching.__enter__() 192 | if patching.new is DEFAULT: 193 | extra_args.append(arg) 194 | args += tuple(extra_args) 195 | try: 196 | return func(*args, **keywargs) 197 | finally: 198 | for patching in getattr(patched, 'patchings', []): 199 | patching.__exit__() 200 | 201 | patched.patchings = [self] 202 | patched.__name__ = func.__name__ 203 | patched.compat_co_firstlineno = getattr(func, "compat_co_firstlineno", 204 | func.func_code.co_firstlineno) 205 | return patched 206 | 207 | 208 | def get_original(self): 209 | target = self.target 210 | name = self.attribute 211 | create = self.create 212 | 213 | original = DEFAULT 214 | if _has_local_attr(target, name): 215 | try: 216 | original = target.__dict__[name] 217 | except AttributeError: 218 | # for instances of classes with slots, they have no __dict__ 219 | original = getattr(target, name) 220 | elif not create and not hasattr(target, name): 221 | raise AttributeError("{0!s} does not have the attribute {1!r}".format(target, name)) 222 | return original 223 | 224 | 225 | def __enter__(self): 226 | new, spec, = self.new, self.spec 227 | original = self.get_original() 228 | if new is DEFAULT: 229 | # XXXX what if original is DEFAULT - shouldn't use it as a spec 230 | inherit = False 231 | if spec == True: 232 | # set spec to the object we are replacing 233 | spec = original 234 | if isinstance(spec, (type, ClassType)): 235 | inherit = True 236 | new = Mock(spec=spec) 237 | if inherit: 238 | new.return_value = Mock(spec=spec) 239 | self.temp_original = original 240 | setattr(self.target, self.attribute, new) 241 | return new 242 | 243 | 244 | def __exit__(self, *_): 245 | if self.temp_original is not DEFAULT: 246 | setattr(self.target, self.attribute, self.temp_original) 247 | else: 248 | delattr(self.target, self.attribute) 249 | del self.temp_original 250 | 251 | 252 | def patch_object(target, attribute, new=DEFAULT, spec=None, create=False): 253 | return _patch(target, attribute, new, spec, create) 254 | 255 | 256 | def patch(target, new=DEFAULT, spec=None, create=False): 257 | try: 258 | target, attribute = target.rsplit('.', 1) 259 | except (TypeError, ValueError): 260 | raise TypeError("Need a valid target to patch. You supplied: {0!r}".format(target,)) 261 | target = _importer(target) 262 | return _patch(target, attribute, new, spec, create) 263 | 264 | 265 | 266 | def _has_local_attr(obj, name): 267 | try: 268 | return name in vars(obj) 269 | except TypeError: 270 | # objects without a __dict__ 271 | return hasattr(obj, name) 272 | -------------------------------------------------------------------------------- /run.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | REM This is how you run it from the command line. 4 | REM You don't actually need this script! 5 | SET RUN_KOANS=python.exe -B contemplate_koans.py 6 | 7 | REM Set this to your python folder: 8 | SET PYTHON_PATH=C:\Python311 9 | 10 | set SCRIPT= 11 | 12 | :loop 13 | 14 | REM Hunt around for python 15 | IF EXIST "python.exe" ( 16 | SET SCRIPT=%RUN_KOANS% 17 | ) ELSE ( 18 | IF EXIST "%PYTHON_PATH%" ( 19 | SET SCRIPT=%PYTHON_PATH%\%RUN_KOANS% 20 | ) ELSE ( 21 | IF EXIST %PYTHON% SET SCRIPT=%PYTHON%\%RUN_KOANS% 22 | ) 23 | ) 24 | 25 | IF NOT "" == "%SCRIPT%" ( 26 | %SCRIPT% 27 | pause 28 | ) ELSE ( 29 | echo. 30 | echo Python.exe is not in the path! 31 | echo. 32 | echo Fix the path and try again. 33 | echo Or better yet, run this with the correct python path: 34 | echo. 35 | echo python.exe contemplate_koans.py 36 | pause 37 | ) 38 | 39 | Set /p keepgoing="Test again? y or n - " 40 | if "%keepgoing%" == "y" ( 41 | goto loop 42 | ) 43 | 44 | :end 45 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | python3 -B contemplate_koans.py 4 | 5 | -------------------------------------------------------------------------------- /runner/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Namespace: runner 5 | 6 | -------------------------------------------------------------------------------- /runner/helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | def cls_name(obj): 5 | return obj.__class__.__name__ -------------------------------------------------------------------------------- /runner/koan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | import re 6 | 7 | # Starting a classname or attribute with an underscore normally implies Private scope. 8 | # However, we are making an exception for __ and ___. 9 | 10 | __all__ = [ "__", "___", "____", "_____", "Koan" ] 11 | 12 | __ = "-=> FILL ME IN! <=-" 13 | 14 | class ___(Exception): 15 | pass 16 | 17 | ____ = "-=> TRUE OR FALSE? <=-" 18 | 19 | _____ = 0 20 | 21 | 22 | class Koan(unittest.TestCase): 23 | pass 24 | -------------------------------------------------------------------------------- /runner/mockable_test_result.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | 6 | # Needed to stop unittest.TestResult itself getting Mocked out of existence, 7 | # which is a problem when testing the helper classes! (It confuses the runner) 8 | 9 | class MockableTestResult(unittest.TestResult): 10 | pass -------------------------------------------------------------------------------- /runner/mountain.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | import sys 6 | 7 | from . import path_to_enlightenment 8 | from .sensei import Sensei 9 | from .writeln_decorator import WritelnDecorator 10 | 11 | class Mountain: 12 | def __init__(self): 13 | self.stream = WritelnDecorator(sys.stdout) 14 | self.tests = path_to_enlightenment.koans() 15 | self.lesson = Sensei(self.stream) 16 | 17 | def walk_the_path(self, args=None): 18 | "Run the koans tests with a custom runner output." 19 | 20 | if args and len(args) >=2: 21 | self.tests = unittest.TestLoader().loadTestsFromName("koans." + args[1]) 22 | 23 | self.tests(self.lesson) 24 | self.lesson.learn() 25 | return self.lesson 26 | -------------------------------------------------------------------------------- /runner/path_to_enlightenment.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | ''' 5 | Functions to load the test cases ("koans") that make up the 6 | Path to Enlightenment. 7 | ''' 8 | 9 | import io 10 | import unittest 11 | 12 | 13 | # The path to enlightenment starts with the following: 14 | KOANS_FILENAME = 'koans.txt' 15 | 16 | 17 | def filter_koan_names(lines): 18 | ''' 19 | Strips leading and trailing whitespace, then filters out blank 20 | lines and comment lines. 21 | ''' 22 | for line in lines: 23 | line = line.strip() 24 | if line.startswith('#'): 25 | continue 26 | if line: 27 | yield line 28 | return 29 | 30 | 31 | def names_from_file(filename): 32 | ''' 33 | Opens the given ``filename`` and yields the fully-qualified names 34 | of TestCases found inside (one per line). 35 | ''' 36 | with io.open(filename, 'rt', encoding='utf8') as names_file: 37 | for name in filter_koan_names(names_file): 38 | yield name 39 | return 40 | 41 | 42 | def koans_suite(names): 43 | ''' 44 | Returns a ``TestSuite`` loaded with all tests found in the given 45 | ``names``, preserving the order in which they are found. 46 | ''' 47 | suite = unittest.TestSuite() 48 | loader = unittest.TestLoader() 49 | loader.sortTestMethodsUsing = None 50 | for name in names: 51 | tests = loader.loadTestsFromName(name) 52 | suite.addTests(tests) 53 | return suite 54 | 55 | 56 | def koans(filename=KOANS_FILENAME): 57 | ''' 58 | Returns a ``TestSuite`` loaded with all the koans (``TestCase``s) 59 | listed in ``filename``. 60 | ''' 61 | names = names_from_file(filename) 62 | return koans_suite(names) 63 | -------------------------------------------------------------------------------- /runner/runner_tests/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Namespace: helpers_tests 5 | -------------------------------------------------------------------------------- /runner/runner_tests/test_helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | 6 | from runner import helper 7 | 8 | class TestHelper(unittest.TestCase): 9 | 10 | def test_that_get_class_name_works_with_a_string_instance(self): 11 | self.assertEqual("str", helper.cls_name(str())) 12 | 13 | def test_that_get_class_name_works_with_a_4(self): 14 | self.assertEquals("int", helper.cls_name(4)) 15 | 16 | def test_that_get_class_name_works_with_a_tuple(self): 17 | self.assertEquals("tuple", helper.cls_name((3,"pie", []))) 18 | -------------------------------------------------------------------------------- /runner/runner_tests/test_mountain.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | from libs.mock import * 6 | 7 | from runner.mountain import Mountain 8 | 9 | class TestMountain(unittest.TestCase): 10 | 11 | def setUp(self): 12 | self.mountain = Mountain() 13 | 14 | def test_it_gets_test_results(self): 15 | with patch_object(self.mountain.stream, 'writeln', Mock()): 16 | with patch_object(self.mountain.lesson, 'learn', Mock()): 17 | self.mountain.walk_the_path() 18 | self.assertTrue(self.mountain.lesson.learn.called) 19 | -------------------------------------------------------------------------------- /runner/runner_tests/test_path_to_enlightenment.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import io 5 | import unittest 6 | 7 | from runner import path_to_enlightenment as pte 8 | 9 | 10 | class TestFilterKoanNames(unittest.TestCase): 11 | 12 | def test_empty_input_produces_empty_output(self): 13 | infile = io.StringIO('') 14 | expected = [] 15 | received = list(pte.filter_koan_names(infile)) 16 | self.assertListEqual(expected, received) 17 | return 18 | 19 | def test_names_yielded_match_names_in_file(self): 20 | names = [ 21 | 'this.is.a.test', 22 | 'this.is.only.a.test', 23 | ] 24 | infile = io.StringIO('\n'.join(names)) 25 | received = list(pte.filter_koan_names(infile)) 26 | self.assertListEqual(names, received) 27 | return 28 | 29 | def test_whitespace_is_stripped(self): 30 | names = [ 31 | 'this.is.a.test', 32 | ' white.space.should.be.stripped', 33 | 'this.is.only.a.test', 34 | 'white.space.should.be.stripped ', 35 | ] 36 | infile = io.StringIO('\n'.join(names)) 37 | expected = [ 38 | 'this.is.a.test', 39 | 'white.space.should.be.stripped', 40 | 'this.is.only.a.test', 41 | 'white.space.should.be.stripped', 42 | ] 43 | received = list(pte.filter_koan_names(infile)) 44 | self.assertListEqual(expected, received) 45 | return 46 | 47 | def test_commented_out_names_are_excluded(self): 48 | names = [ 49 | 'this.is.a.test', 50 | '#this.is.a.comment', 51 | 'this.is.only.a.test', 52 | ' # this.is.also a.comment ', 53 | ] 54 | infile = io.StringIO('\n'.join(names)) 55 | expected = [ 56 | 'this.is.a.test', 57 | 'this.is.only.a.test', 58 | ] 59 | received = list(pte.filter_koan_names(infile)) 60 | self.assertListEqual(expected, received) 61 | return 62 | 63 | def all_blank_or_comment_lines_produce_empty_output(self): 64 | names = [ 65 | ' ', 66 | '# This is a comment.', 67 | '\t', 68 | ' # This is also a comment.', 69 | ] 70 | infile = io.StringIO('\n'.join(names)) 71 | expected = [] 72 | received = list(pte.filter_koan_names(infile)) 73 | self.assertListEqual(expected, received) 74 | return 75 | 76 | 77 | class TestKoansSuite(unittest.TestCase): 78 | 79 | def test_empty_input_produces_empty_testsuite(self): 80 | names = [] 81 | suite = pte.koans_suite(names) 82 | self.assertTrue(isinstance(suite, unittest.TestSuite)) 83 | expected = [] 84 | received = list(suite) 85 | self.assertListEqual(expected, received) 86 | return 87 | 88 | def test_testcase_names_appear_in_testsuite(self): 89 | names = [ 90 | 'koans.about_asserts.AboutAsserts', 91 | 'koans.about_none.AboutNone', 92 | 'koans.about_strings.AboutStrings', 93 | ] 94 | suite = pte.koans_suite(names) 95 | self.assertTrue(isinstance(suite, unittest.TestSuite)) 96 | expected = [ 97 | 'AboutAsserts', 98 | 'AboutNone', 99 | 'AboutStrings', 100 | ] 101 | received = sorted(set(test.__class__.__name__ for test in suite)) 102 | self.assertListEqual(expected, received) 103 | return 104 | -------------------------------------------------------------------------------- /runner/runner_tests/test_sensei.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | import re 6 | 7 | from libs.mock import * 8 | 9 | from runner.sensei import Sensei 10 | from runner.writeln_decorator import WritelnDecorator 11 | from runner.mockable_test_result import MockableTestResult 12 | 13 | class AboutParrots: 14 | pass 15 | class AboutLumberjacks: 16 | pass 17 | class AboutTennis: 18 | pass 19 | class AboutTheKnightsWhoSayNi: 20 | pass 21 | class AboutMrGumby: 22 | pass 23 | class AboutMessiahs: 24 | pass 25 | class AboutGiantFeet: 26 | pass 27 | class AboutTrebuchets: 28 | pass 29 | class AboutFreemasons: 30 | pass 31 | 32 | error_assertion_with_message = """Traceback (most recent call last): 33 | File "/Users/Greg/hg/python_koans/koans/about_exploding_trousers.py", line 43, in test_durability 34 | self.assertEqual("Steel","Lard", "Another fine mess you've got me into Stanley...") 35 | AssertionError: Another fine mess you've got me into Stanley...""" 36 | 37 | error_assertion_equals = """ 38 | 39 | Traceback (most recent call last): 40 | File "/Users/Greg/hg/python_koans/koans/about_exploding_trousers.py", line 49, in test_math 41 | self.assertEqual(4,99) 42 | AssertionError: 4 != 99 43 | """ 44 | 45 | error_assertion_true = """Traceback (most recent call last): 46 | File "/Users/Greg/hg/python_koans/koans/about_armories.py", line 25, in test_weoponary 47 | self.assertTrue("Pen" > "Sword") 48 | AssertionError 49 | 50 | """ 51 | 52 | error_mess = """ 53 | Traceback (most recent call last): 54 | File "contemplate_koans.py", line 5, in 55 | from runner.mountain import Mountain 56 | File "/Users/Greg/hg/python_koans/runner/mountain.py", line 7, in 57 | import path_to_enlightenment 58 | File "/Users/Greg/hg/python_koans/runner/path_to_enlightenment.py", line 8, in 59 | from koans import * 60 | File "/Users/Greg/hg/python_koans/koans/about_asserts.py", line 20 61 | self.assertTrue(eoe"Pen" > "Sword", "nhnth") 62 | ^ 63 | SyntaxError: invalid syntax""" 64 | 65 | error_with_list = """Traceback (most recent call last): 66 | File "/Users/Greg/hg/python_koans/koans/about_armories.py", line 84, in test_weoponary 67 | self.assertEqual([1, 9], [1, 2]) 68 | AssertionError: Lists differ: [1, 9] != [1, 2] 69 | 70 | First differing element 1: 71 | 9 72 | 2 73 | 74 | - [1, 9] 75 | ? ^ 76 | 77 | + [1, 2] 78 | ? ^ 79 | 80 | """ 81 | 82 | 83 | class TestSensei(unittest.TestCase): 84 | 85 | def setUp(self): 86 | self.sensei = Sensei(WritelnDecorator(Mock())) 87 | 88 | def test_that_it_successes_only_count_if_passes_are_currently_allowed(self): 89 | with patch('runner.mockable_test_result.MockableTestResult.addSuccess', Mock()): 90 | self.sensei.passesCount = Mock() 91 | self.sensei.addSuccess(Mock()) 92 | self.assertTrue(self.sensei.passesCount.called) 93 | 94 | def test_that_it_increases_the_passes_on_every_success(self): 95 | with patch('runner.mockable_test_result.MockableTestResult.addSuccess', Mock()): 96 | pass_count = self.sensei.pass_count 97 | self.sensei.addSuccess(Mock()) 98 | self.assertEqual(pass_count + 1, self.sensei.pass_count) 99 | 100 | def test_that_nothing_is_returned_as_sorted_result_if_there_are_no_failures(self): 101 | self.sensei.failures = [] 102 | self.assertEqual(None, self.sensei.sortFailures("AboutLife")) 103 | 104 | def test_that_nothing_is_returned_as_sorted_result_if_there_are_no_relevent_failures(self): 105 | self.sensei.failures = [ 106 | (AboutTheKnightsWhoSayNi(),"File 'about_the_knights_whn_say_ni.py', line 24"), 107 | (AboutMessiahs(),"File 'about_messiahs.py', line 43"), 108 | (AboutMessiahs(),"File 'about_messiahs.py', line 844") 109 | ] 110 | self.assertEqual(None, self.sensei.sortFailures("AboutLife")) 111 | 112 | def test_that_nothing_is_returned_as_sorted_result_if_there_are_3_shuffled_results(self): 113 | self.sensei.failures = [ 114 | (AboutTennis(),"File 'about_tennis.py', line 299"), 115 | (AboutTheKnightsWhoSayNi(),"File 'about_the_knights_whn_say_ni.py', line 24"), 116 | (AboutTennis(),"File 'about_tennis.py', line 30"), 117 | (AboutMessiahs(),"File 'about_messiahs.py', line 43"), 118 | (AboutTennis(),"File 'about_tennis.py', line 2"), 119 | (AboutMrGumby(),"File 'about_mr_gumby.py', line odd"), 120 | (AboutMessiahs(),"File 'about_messiahs.py', line 844") 121 | ] 122 | 123 | results = self.sensei.sortFailures("AboutTennis") 124 | self.assertEqual(3, len(results)) 125 | self.assertEqual(2, results[0][0]) 126 | self.assertEqual(30, results[1][0]) 127 | self.assertEqual(299, results[2][0]) 128 | 129 | def test_that_it_will_choose_not_find_anything_with_non_standard_error_trace_string(self): 130 | self.sensei.failures = [ 131 | (AboutMrGumby(),"File 'about_mr_gumby.py', line MISSING"), 132 | ] 133 | self.assertEqual(None, self.sensei.sortFailures("AboutMrGumby")) 134 | 135 | 136 | def test_that_it_will_choose_correct_first_result_with_lines_9_and_27(self): 137 | self.sensei.failures = [ 138 | (AboutTrebuchets(),"File 'about_trebuchets.py', line 27"), 139 | (AboutTrebuchets(),"File 'about_trebuchets.py', line 9"), 140 | (AboutTrebuchets(),"File 'about_trebuchets.py', line 73v") 141 | ] 142 | self.assertEqual("File 'about_trebuchets.py', line 9", self.sensei.firstFailure()[1]) 143 | 144 | def test_that_it_will_choose_correct_first_result_with_multiline_test_classes(self): 145 | self.sensei.failures = [ 146 | (AboutGiantFeet(),"File 'about_giant_feet.py', line 999"), 147 | (AboutGiantFeet(),"File 'about_giant_feet.py', line 44"), 148 | (AboutFreemasons(),"File 'about_freemasons.py', line 1"), 149 | (AboutFreemasons(),"File 'about_freemasons.py', line 11") 150 | ] 151 | self.assertEqual("File 'about_giant_feet.py', line 44", self.sensei.firstFailure()[1]) 152 | 153 | def test_that_error_report_features_a_stack_dump(self): 154 | self.sensei.scrapeInterestingStackDump = Mock() 155 | self.sensei.firstFailure = Mock() 156 | self.sensei.firstFailure.return_value = (Mock(), "FAILED") 157 | self.sensei.errorReport() 158 | self.assertTrue(self.sensei.scrapeInterestingStackDump.called) 159 | 160 | def test_that_scraping_the_assertion_error_with_nothing_gives_you_a_blank_back(self): 161 | self.assertEqual("", self.sensei.scrapeAssertionError(None)) 162 | 163 | def test_that_scraping_the_assertion_error_with_messaged_assert(self): 164 | self.assertEqual(" AssertionError: Another fine mess you've got me into Stanley...", 165 | self.sensei.scrapeAssertionError(error_assertion_with_message)) 166 | 167 | def test_that_scraping_the_assertion_error_with_assert_equals(self): 168 | self.assertEqual(" AssertionError: 4 != 99", 169 | self.sensei.scrapeAssertionError(error_assertion_equals)) 170 | 171 | def test_that_scraping_the_assertion_error_with_assert_true(self): 172 | self.assertEqual(" AssertionError", 173 | self.sensei.scrapeAssertionError(error_assertion_true)) 174 | 175 | def test_that_scraping_the_assertion_error_with_syntax_error(self): 176 | self.assertEqual(" SyntaxError: invalid syntax", 177 | self.sensei.scrapeAssertionError(error_mess)) 178 | 179 | def test_that_scraping_the_assertion_error_with_list_error(self): 180 | self.assertEqual(""" AssertionError: Lists differ: [1, 9] != [1, 2] 181 | 182 | First differing element 1: 183 | 9 184 | 2 185 | 186 | - [1, 9] 187 | ? ^ 188 | 189 | + [1, 2] 190 | ? ^""", 191 | self.sensei.scrapeAssertionError(error_with_list)) 192 | 193 | def test_that_scraping_a_non_existent_stack_dump_gives_you_nothing(self): 194 | self.assertEqual("", self.sensei.scrapeInterestingStackDump(None)) 195 | 196 | def test_that_if_there_are_no_failures_say_the_final_zenlike_remark(self): 197 | self.sensei.failures = None 198 | words = self.sensei.say_something_zenlike() 199 | 200 | m = re.search("Spanish Inquisition", words) 201 | self.assertTrue(m and m.group(0)) 202 | 203 | def test_that_if_there_are_0_successes_it_will_say_the_first_zen_of_python_koans(self): 204 | self.sensei.pass_count = 0 205 | self.sensei.failures = Mock() 206 | words = self.sensei.say_something_zenlike() 207 | m = re.search("Beautiful is better than ugly", words) 208 | self.assertTrue(m and m.group(0)) 209 | 210 | def test_that_if_there_is_1_success_it_will_say_the_second_zen_of_python_koans(self): 211 | self.sensei.pass_count = 1 212 | self.sensei.failures = Mock() 213 | words = self.sensei.say_something_zenlike() 214 | m = re.search("Explicit is better than implicit", words) 215 | self.assertTrue(m and m.group(0)) 216 | 217 | def test_that_if_there_are_10_successes_it_will_say_the_sixth_zen_of_python_koans(self): 218 | self.sensei.pass_count = 10 219 | self.sensei.failures = Mock() 220 | words = self.sensei.say_something_zenlike() 221 | m = re.search("Sparse is better than dense", words) 222 | self.assertTrue(m and m.group(0)) 223 | 224 | def test_that_if_there_are_36_successes_it_will_say_the_final_zen_of_python_koans(self): 225 | self.sensei.pass_count = 36 226 | self.sensei.failures = Mock() 227 | words = self.sensei.say_something_zenlike() 228 | m = re.search("Namespaces are one honking great idea", words) 229 | self.assertTrue(m and m.group(0)) 230 | 231 | def test_that_if_there_are_37_successes_it_will_say_the_first_zen_of_python_koans_again(self): 232 | self.sensei.pass_count = 37 233 | self.sensei.failures = Mock() 234 | words = self.sensei.say_something_zenlike() 235 | m = re.search("Beautiful is better than ugly", words) 236 | self.assertTrue(m and m.group(0)) 237 | 238 | def test_that_total_lessons_return_7_if_there_are_7_lessons(self): 239 | self.sensei.filter_all_lessons = Mock() 240 | self.sensei.filter_all_lessons.return_value = [1,2,3,4,5,6,7] 241 | self.assertEqual(7, self.sensei.total_lessons()) 242 | 243 | def test_that_total_lessons_return_0_if_all_lessons_is_none(self): 244 | self.sensei.filter_all_lessons = Mock() 245 | self.sensei.filter_all_lessons.return_value = None 246 | self.assertEqual(0, self.sensei.total_lessons()) 247 | 248 | def test_total_koans_return_43_if_there_are_43_test_cases(self): 249 | self.sensei.tests.countTestCases = Mock() 250 | self.sensei.tests.countTestCases.return_value = 43 251 | self.assertEqual(43, self.sensei.total_koans()) 252 | 253 | def test_filter_all_lessons_will_discover_test_classes_if_none_have_been_discovered_yet(self): 254 | self.sensei.all_lessons = 0 255 | self.assertTrue(len(self.sensei.filter_all_lessons()) > 10) 256 | self.assertTrue(len(self.sensei.all_lessons) > 10) 257 | -------------------------------------------------------------------------------- /runner/sensei.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import unittest 5 | import re 6 | import sys 7 | import os 8 | import glob 9 | 10 | from . import helper 11 | from .mockable_test_result import MockableTestResult 12 | from runner import path_to_enlightenment 13 | 14 | from libs.colorama import init, Fore, Style 15 | init() # init colorama 16 | 17 | class Sensei(MockableTestResult): 18 | def __init__(self, stream): 19 | unittest.TestResult.__init__(self) 20 | self.stream = stream 21 | self.prevTestClassName = None 22 | self.tests = path_to_enlightenment.koans() 23 | self.pass_count = 0 24 | self.lesson_pass_count = 0 25 | self.all_lessons = None 26 | 27 | def startTest(self, test): 28 | MockableTestResult.startTest(self, test) 29 | 30 | if helper.cls_name(test) != self.prevTestClassName: 31 | self.prevTestClassName = helper.cls_name(test) 32 | if not self.failures: 33 | self.stream.writeln() 34 | self.stream.writeln("{0}{1}Thinking {2}".format( 35 | Fore.RESET, Style.NORMAL, helper.cls_name(test))) 36 | if helper.cls_name(test) not in ['AboutAsserts', 'AboutExtraCredit']: 37 | self.lesson_pass_count += 1 38 | 39 | def addSuccess(self, test): 40 | if self.passesCount(): 41 | MockableTestResult.addSuccess(self, test) 42 | self.stream.writeln( \ 43 | " {0}{1}{2} has expanded your awareness.{3}{4}" \ 44 | .format(Fore.GREEN, Style.BRIGHT, test._testMethodName, \ 45 | Fore.RESET, Style.NORMAL)) 46 | self.pass_count += 1 47 | 48 | def addError(self, test, err): 49 | # Having 1 list for errors and 1 list for failures would mess with 50 | # the error sequence 51 | self.addFailure(test, err) 52 | 53 | def passesCount(self): 54 | return not (self.failures and helper.cls_name(self.failures[0][0]) != self.prevTestClassName) 55 | 56 | def addFailure(self, test, err): 57 | MockableTestResult.addFailure(self, test, err) 58 | 59 | def sortFailures(self, testClassName): 60 | table = list() 61 | for test, err in self.failures: 62 | if helper.cls_name(test) == testClassName: 63 | m = re.search("(?<= line )\d+" ,err) 64 | if m: 65 | tup = (int(m.group(0)), test, err) 66 | table.append(tup) 67 | 68 | if table: 69 | return sorted(table) 70 | else: 71 | return None 72 | 73 | def firstFailure(self): 74 | if not self.failures: return None 75 | 76 | table = self.sortFailures(helper.cls_name(self.failures[0][0])) 77 | 78 | if table: 79 | return (table[0][1], table[0][2]) 80 | else: 81 | return None 82 | 83 | def learn(self): 84 | self.errorReport() 85 | 86 | self.stream.writeln("") 87 | self.stream.writeln("") 88 | self.stream.writeln(self.report_progress()) 89 | if self.failures: 90 | self.stream.writeln(self.report_remaining()) 91 | self.stream.writeln("") 92 | self.stream.writeln(self.say_something_zenlike()) 93 | 94 | if self.failures: sys.exit(-1) 95 | self.stream.writeln( 96 | "\n{0}**************************************************" \ 97 | .format(Fore.RESET)) 98 | self.stream.writeln("\n{0}That was the last one, well done!" \ 99 | .format(Fore.MAGENTA)) 100 | self.stream.writeln( 101 | "\nIf you want more, take a look at about_extra_credit.py{0}{1}" \ 102 | .format(Fore.RESET, Style.NORMAL)) 103 | 104 | def errorReport(self): 105 | problem = self.firstFailure() 106 | if not problem: return 107 | test, err = problem 108 | self.stream.writeln(" {0}{1}{2} has damaged your " 109 | "karma.".format(Fore.RED, Style.BRIGHT, test._testMethodName)) 110 | 111 | self.stream.writeln("\n{0}{1}You have not yet reached enlightenment ..." \ 112 | .format(Fore.RESET, Style.NORMAL)) 113 | self.stream.writeln("{0}{1}{2}".format(Fore.RED, \ 114 | Style.BRIGHT, self.scrapeAssertionError(err))) 115 | self.stream.writeln("") 116 | self.stream.writeln("{0}{1}Please meditate on the following code:" \ 117 | .format(Fore.RESET, Style.NORMAL)) 118 | self.stream.writeln("{0}{1}{2}{3}{4}".format(Fore.YELLOW, Style.BRIGHT, \ 119 | self.scrapeInterestingStackDump(err), Fore.RESET, Style.NORMAL)) 120 | 121 | def scrapeAssertionError(self, err): 122 | if not err: return "" 123 | 124 | error_text = "" 125 | count = 0 126 | for line in err.splitlines(): 127 | m = re.search("^[^^ ].*$",line) 128 | if m and m.group(0): 129 | count+=1 130 | 131 | if count>1: 132 | error_text += (" " + line.strip()).rstrip() + '\n' 133 | return error_text.strip('\n') 134 | 135 | def scrapeInterestingStackDump(self, err): 136 | if not err: 137 | return "" 138 | 139 | lines = err.splitlines() 140 | 141 | sep = '@@@@@SEP@@@@@' 142 | 143 | stack_text = "" 144 | for line in lines: 145 | m = re.search("^ File .*$",line) 146 | if m and m.group(0): 147 | stack_text += '\n' + line 148 | 149 | m = re.search("^ \w(\w)+.*$",line) 150 | if m and m.group(0): 151 | stack_text += sep + line 152 | 153 | lines = stack_text.splitlines() 154 | 155 | stack_text = "" 156 | for line in lines: 157 | m = re.search("^.*[/\\\\]koans[/\\\\].*$",line) 158 | if m and m.group(0): 159 | stack_text += line + '\n' 160 | 161 | 162 | stack_text = stack_text.replace(sep, '\n').strip('\n') 163 | stack_text = re.sub(r'(about_\w+.py)', 164 | r"{0}\1{1}".format(Fore.BLUE, Fore.YELLOW), stack_text) 165 | stack_text = re.sub(r'(line \d+)', 166 | r"{0}\1{1}".format(Fore.BLUE, Fore.YELLOW), stack_text) 167 | return stack_text 168 | 169 | def report_progress(self): 170 | return "You have completed {0} ({2} %) koans and " \ 171 | "{1} (out of {3}) lessons.".format( 172 | self.pass_count, 173 | self.lesson_pass_count, 174 | self.pass_count*100//self.total_koans(), 175 | self.total_lessons()) 176 | 177 | def report_remaining(self): 178 | koans_remaining = self.total_koans() - self.pass_count 179 | lessons_remaining = self.total_lessons() - self.lesson_pass_count 180 | 181 | return "You are now {0} koans and {1} lessons away from " \ 182 | "reaching enlightenment.".format( 183 | koans_remaining, 184 | lessons_remaining) 185 | 186 | # Hat's tip to Tim Peters for the zen statements from The 'Zen 187 | # of Python' (http://www.python.org/dev/peps/pep-0020/) 188 | # 189 | # Also a hat's tip to Ara T. Howard for the zen statements from his 190 | # metakoans Ruby Quiz (http://rubyquiz.com/quiz67.html) and 191 | # Edgecase's later permutation in the Ruby Koans 192 | def say_something_zenlike(self): 193 | if self.failures: 194 | turn = self.pass_count % 37 195 | 196 | zenness = ""; 197 | if turn == 0: 198 | zenness = "Beautiful is better than ugly." 199 | elif turn == 1 or turn == 2: 200 | zenness = "Explicit is better than implicit." 201 | elif turn == 3 or turn == 4: 202 | zenness = "Simple is better than complex." 203 | elif turn == 5 or turn == 6: 204 | zenness = "Complex is better than complicated." 205 | elif turn == 7 or turn == 8: 206 | zenness = "Flat is better than nested." 207 | elif turn == 9 or turn == 10: 208 | zenness = "Sparse is better than dense." 209 | elif turn == 11 or turn == 12: 210 | zenness = "Readability counts." 211 | elif turn == 13 or turn == 14: 212 | zenness = "Special cases aren't special enough to " \ 213 | "break the rules." 214 | elif turn == 15 or turn == 16: 215 | zenness = "Although practicality beats purity." 216 | elif turn == 17 or turn == 18: 217 | zenness = "Errors should never pass silently." 218 | elif turn == 19 or turn == 20: 219 | zenness = "Unless explicitly silenced." 220 | elif turn == 21 or turn == 22: 221 | zenness = "In the face of ambiguity, refuse the " \ 222 | "temptation to guess." 223 | elif turn == 23 or turn == 24: 224 | zenness = "There should be one-- and preferably only " \ 225 | "one --obvious way to do it." 226 | elif turn == 25 or turn == 26: 227 | zenness = "Although that way may not be obvious at " \ 228 | "first unless you're Dutch." 229 | elif turn == 27 or turn == 28: 230 | zenness = "Now is better than never." 231 | elif turn == 29 or turn == 30: 232 | zenness = "Although never is often better than right " \ 233 | "now." 234 | elif turn == 31 or turn == 32: 235 | zenness = "If the implementation is hard to explain, " \ 236 | "it's a bad idea." 237 | elif turn == 33 or turn == 34: 238 | zenness = "If the implementation is easy to explain, " \ 239 | "it may be a good idea." 240 | else: 241 | zenness = "Namespaces are one honking great idea -- " \ 242 | "let's do more of those!" 243 | return "{0}{1}{2}{3}".format(Fore.CYAN, zenness, Fore.RESET, Style.NORMAL); 244 | else: 245 | return "{0}Nobody ever expects the Spanish Inquisition." \ 246 | .format(Fore.CYAN) 247 | 248 | # Hopefully this will never ever happen! 249 | return "The temple is collapsing! Run!!!" 250 | 251 | def total_lessons(self): 252 | all_lessons = self.filter_all_lessons() 253 | if all_lessons: 254 | return len(all_lessons) 255 | else: 256 | return 0 257 | 258 | def total_koans(self): 259 | return self.tests.countTestCases() 260 | 261 | def filter_all_lessons(self): 262 | cur_dir = os.path.split(os.path.realpath(__file__))[0] 263 | if not self.all_lessons: 264 | self.all_lessons = glob.glob('{0}/../koans/about*.py'.format(cur_dir)) 265 | self.all_lessons = list(filter(lambda filename: 266 | "about_extra_credit" not in filename, 267 | self.all_lessons)) 268 | 269 | return self.all_lessons 270 | -------------------------------------------------------------------------------- /runner/writeln_decorator.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # encoding: utf-8 3 | 4 | import sys 5 | import os 6 | 7 | # Taken from legacy python unittest 8 | class WritelnDecorator: 9 | """Used to decorate file-like objects with a handy 'writeln' method""" 10 | def __init__(self,stream): 11 | self.stream = stream 12 | 13 | def __getattr__(self, attr): 14 | return getattr(self.stream,attr) 15 | 16 | def writeln(self, arg=None): 17 | if arg: self.write(arg) 18 | self.write('\n') # text-mode streams translate to \r\n if needed 19 | 20 | -------------------------------------------------------------------------------- /scent.py: -------------------------------------------------------------------------------- 1 | from sniffer.api import * 2 | import os 3 | 4 | watch_paths = ['.', 'koans/'] 5 | 6 | @file_validator 7 | def py_files(filename): 8 | return filename.endswith('.py') and not os.path.basename(filename).startswith('.') 9 | 10 | @runnable 11 | def execute_koans(*args): 12 | os.system('python3 -B contemplate_koans.py') 13 | --------------------------------------------------------------------------------