diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 00000000000..b4c8efe0704 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @exercism/python diff --git a/.travis.yml b/.travis.yml index 143abfb8e2e..a4dc35d2b2b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,7 +16,12 @@ matrix: - env: JOB=HOUSEKEEPING install: ./bin/fetch-configlet before_script: ./bin/check-readmes.sh - script: ./bin/configlet lint . + script: + # May provide more useful output than configlet fmt + # if JSON is not valid or items are incomplete + - ./bin/configlet lint . + # Verify correct formatting in config.json + - ./bin/configlet fmt . && git diff --quiet allow_failures: - python: nightly diff --git a/README.md b/README.md index 81bb5a064de..a38a63b16e6 100644 --- a/README.md +++ b/README.md @@ -72,9 +72,5 @@ If you're interested, Tim Pope even has an [entire blog post](http://tbaggery.co If you're new to Git, take a look at [this short guide](https://github.com/exercism/docs/blob/master/contributing-to-language-tracks/README.md#git-basics). - -## Python icon -The Python logo is an unregistered trademark. We are using a derived logo with the permission of the Python Software Foundation. - ## License This repository uses the [MIT License](/LICENSE). diff --git a/config.json b/config.json index dd10b90c1c5..f80b140e3fd 100644 --- a/config.json +++ b/config.json @@ -1423,7 +1423,7 @@ ] }, { - "slug": "retree", + "slug": "satellite", "uuid": "18467e72-e3b0-403c-896a-ce337f96e6f7", "core": false, "unlocked_by": "markdown", @@ -1554,6 +1554,16 @@ "topics": [ "arrays" ] + }, + { + "slug": "resistor-colors", + "uuid": "089f06a6-0759-479c-8c00-d699525a1e22", + "core": false, + "unlocked_by": null, + "difficulty": 1, + "topics": [ + "arrays" + ] } ] } diff --git a/exercises/darts/darts_test.py b/exercises/darts/darts_test.py index 53955290986..1b359709095 100644 --- a/exercises/darts/darts_test.py +++ b/exercises/darts/darts_test.py @@ -2,7 +2,7 @@ from darts import score -# Tests adapted from 'problem-specifications//canonical-data.json' @ v1.1.0 +# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0 class darts_test(unittest.TestCase): def test_dart_lands_outside_target(self): diff --git a/exercises/leap/leap_test.py b/exercises/leap/leap_test.py index 73c2e43a457..92ea304e617 100644 --- a/exercises/leap/leap_test.py +++ b/exercises/leap/leap_test.py @@ -3,12 +3,16 @@ from leap import is_leap_year -# Tests adapted from `problem-specifications//canonical-data.json` @ v1.4.0 +# Tests adapted from `problem-specifications//canonical-data.json` @ v1.5.1 + class LeapTest(unittest.TestCase): def test_year_not_divisible_by_4(self): self.assertIs(is_leap_year(2015), False) + def test_year_divisible_by_2_not_divisible_by_4(self): + self.assertIs(is_leap_year(1970), False) + def test_year_divisible_by_4_not_divisible_by_100(self): self.assertIs(is_leap_year(1996), True) diff --git a/exercises/palindrome-products/palindrome_products_test.py b/exercises/palindrome-products/palindrome_products_test.py index 200ab5686dc..11ece4dc154 100644 --- a/exercises/palindrome-products/palindrome_products_test.py +++ b/exercises/palindrome-products/palindrome_products_test.py @@ -62,12 +62,12 @@ def test_largest_palindrome_from_four_digit_factors(self): def test_empty_for_smallest_palindrome_if_none_in_range(self): value, factors = smallest_palindrome(min_factor=1002, max_factor=1003) self.assertIsNone(value) - self.assertEqual(factors, []) + self.assertFactorsEqual(factors, []) def test_empty_for_largest_palindrome_if_none_in_range(self): value, factors = largest_palindrome(min_factor=15, max_factor=15) self.assertIsNone(value) - self.assertEqual(factors, []) + self.assertFactorsEqual(factors, []) def test_error_for_smallest_if_min_is_more_than_max(self): with self.assertRaisesWithMessage(ValueError): diff --git a/exercises/rational-numbers/rational_numbers_test.py b/exercises/rational-numbers/rational_numbers_test.py index 081feb2fec1..a7fe7812f5a 100644 --- a/exercises/rational-numbers/rational_numbers_test.py +++ b/exercises/rational-numbers/rational_numbers_test.py @@ -5,7 +5,7 @@ from rational_numbers import Rational -# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0 +# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.0 class RationalNumbersTest(unittest.TestCase): @@ -71,9 +71,15 @@ def test_divide_by_one(self): def test_absolute_value_of_positive(self): self.assertEqual(abs(Rational(1, 2)), Rational(1, 2)) + def test_absolute_value_of_positive_negative_numerator_denominator(self): + self.assertEqual(abs(Rational(-1, -2)), Rational(1, 2)) + def test_absolute_value_of_negative(self): self.assertEqual(abs(Rational(-1, 2)), Rational(1, 2)) + def test_absolute_value_of_negative_with_negative_denominator(self): + self.assertEqual(abs(Rational(1, -2)), Rational(1, 2)) + def test_absolute_value_of_zero(self): self.assertEqual(abs(Rational(0, 1)), Rational(0, 1)) diff --git a/exercises/resistor-colors/README.md b/exercises/resistor-colors/README.md new file mode 100644 index 00000000000..aa49ca44cb0 --- /dev/null +++ b/exercises/resistor-colors/README.md @@ -0,0 +1,72 @@ +# Resistor Colors + +If you want to build something using a Raspberry Pi, you'll probably use _resistors_. For this exercise, you need to know two things about them: + +* Each resistor has a resistance value. +* Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read. +To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values. Each band acts as a digit of a number. For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15. + +In this exercise, you are going to create a helpful program so that you don't have to remember the values of the bands. The program will take two colors as input, and output the correct number. + +The band colors are encoded as follows: + +- Black: 0 +- Brown: 1 +- Red: 2 +- Orange: 3 +- Yellow: 4 +- Green: 5 +- Blue: 6 +- Violet: 7 +- Grey: 8 +- White: 9 + + +## Exception messages + +Sometimes it is necessary to raise an exception. When you do this, you should include a meaningful error message to +indicate what the source of the error is. This makes your code more readable and helps significantly with debugging. Not +every exercise will require you to raise an exception, but for those that do, the tests will only pass if you include +a message. + +To raise a message with an exception, just write it as an argument to the exception type. For example, instead of +`raise Exception`, you should write: + +```python +raise Exception("Meaningful message indicating the source of the error") +``` + +## Running the tests + +To run the tests, run the appropriate command below ([why they are different](https://github.com/pytest-dev/pytest/issues/1629#issue-161422224)): + +- Python 2.7: `py.test resistor_colors_test.py` +- Python 3.4+: `pytest resistor_colors_test.py` + +Alternatively, you can tell Python to run the pytest module (allowing the same command to be used regardless of Python version): +`python -m pytest resistor_colors_test.py` + +### Common `pytest` options + +- `-v` : enable verbose output +- `-x` : stop running tests on first failure +- `--ff` : run failures from previous test before running other test cases + +For other options, see `python -m pytest -h` + +## Submitting Exercises + +Note that, when trying to submit an exercise, make sure the solution is in the `$EXERCISM_WORKSPACE/python/resistor-colors` directory. + +You can find your Exercism workspace by running `exercism debug` and looking for the line that starts with `Workspace`. + +For more detailed information about running tests, code style and linting, +please see [Running the Tests](http://exercism.io/tracks/python/tests). + +## Source + +Maud de Vries, Erik Schierboom [https://github.com/exercism/problem-specifications/issues/1464](https://github.com/exercism/problem-specifications/issues/1464) + +## Submitting Incomplete Solutions + +It's possible to submit an incomplete solution so you can see how others have completed the exercise. diff --git a/exercises/resistor-colors/example.py b/exercises/resistor-colors/example.py new file mode 100644 index 00000000000..0f128593925 --- /dev/null +++ b/exercises/resistor-colors/example.py @@ -0,0 +1,16 @@ +COLORS = [ + 'black', + 'brown', + 'red', + 'orange', + 'yellow', + 'green', + 'blue', + 'violet', + 'grey', + 'white' +] + + +def value(colors): + return 10 * COLORS.index(colors[0]) + COLORS.index(colors[1]) diff --git a/exercises/resistor-colors/resistor_colors.py b/exercises/resistor-colors/resistor_colors.py new file mode 100644 index 00000000000..f83a1c237cb --- /dev/null +++ b/exercises/resistor-colors/resistor_colors.py @@ -0,0 +1,2 @@ +def value(colors): + pass diff --git a/exercises/resistor-colors/resistor_colors_test.py b/exercises/resistor-colors/resistor_colors_test.py new file mode 100644 index 00000000000..f3681dad4c4 --- /dev/null +++ b/exercises/resistor-colors/resistor_colors_test.py @@ -0,0 +1,23 @@ +import unittest + +from resistor_colors import value + + +# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0 + +class ResistorColorsTest(unittest.TestCase): + def test_brown_and_black(self): + self.assertEqual(value(['brown', 'black']), 10) + + def test_blue_and_grey(self): + self.assertEqual(value(['blue', 'grey']), 68) + + def test_yellow_and_violet(self): + self.assertEqual(value(['yellow', 'violet']), 47) + + def test_orange_and_orange(self): + self.assertEqual(value(['orange', 'orange']), 33) + + +if __name__ == '__main__': + unittest.main() diff --git a/exercises/rest-api/rest_api_test.py b/exercises/rest-api/rest_api_test.py index d98a2a72886..f6c1886cb58 100644 --- a/exercises/rest-api/rest_api_test.py +++ b/exercises/rest-api/rest_api_test.py @@ -4,7 +4,7 @@ from rest_api import RestAPI -# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.2 +# Tests adapted from `problem-specifications//canonical-data.json` @ v1.1.1 class RestAPITest(unittest.TestCase): def test_no_users(self): @@ -325,6 +325,56 @@ def test_lender_owes_borrower_less_than_new_loan(self): } self.assertDictEqual(json.loads(response), expected) + def test_lender_owes_borrower_same_as_new_loan(self): + database = { + "users": [ + { + "name": "Adam", + "owes": { + "Bob": 3.0 + }, + "owed_by": {}, + "balance": -3.0 + }, + { + "name": "Bob", + "owes": {}, + "owed_by": { + "Adam": 3.0 + }, + "balance": 3.0 + } + ] + } + api = RestAPI(database) + payload = json.dumps({ + 'lender': 'Adam', + 'borrower': 'Bob', + 'amount': 3.0 + }) + response = api.post('/iou', payload) + expected = { + 'users': [ + { + "name": "Adam", + "owes": { + }, + "owed_by": { + }, + "balance": 0.0 + }, + { + "name": "Bob", + "owes": { + }, + "owed_by": { + }, + "balance": 0.0 + } + ] + } + self.assertDictEqual(json.loads(response), expected) + if __name__ == '__main__': unittest.main() diff --git a/exercises/retree/README.md b/exercises/satellite/README.md similarity index 100% rename from exercises/retree/README.md rename to exercises/satellite/README.md diff --git a/exercises/retree/example.py b/exercises/satellite/example.py similarity index 100% rename from exercises/retree/example.py rename to exercises/satellite/example.py diff --git a/exercises/retree/retree.py b/exercises/satellite/satellite.py similarity index 100% rename from exercises/retree/retree.py rename to exercises/satellite/satellite.py diff --git a/exercises/retree/retree_test.py b/exercises/satellite/satellite_test.py similarity index 95% rename from exercises/retree/retree_test.py rename to exercises/satellite/satellite_test.py index 411dfbfc0ed..196cf1c917b 100644 --- a/exercises/retree/retree_test.py +++ b/exercises/satellite/satellite_test.py @@ -1,11 +1,11 @@ import unittest -from retree import tree_from_traversals +from satellite import tree_from_traversals -# Tests adapted from `problem-specifications//canonical-data.json` @ v1.0.0 +# Tests adapted from `problem-specifications//canonical-data.json` @ v2.0.0 -class TwoFerTest(unittest.TestCase): +class SatelliteTest(unittest.TestCase): def test_empty_tree(self): preorder = [] inorder = [] diff --git a/exercises/simple-cipher/simple_cipher_test.py b/exercises/simple-cipher/simple_cipher_test.py index b54f4e24b26..6e8839cd752 100644 --- a/exercises/simple-cipher/simple_cipher_test.py +++ b/exercises/simple-cipher/simple_cipher_test.py @@ -4,7 +4,7 @@ from simple_cipher import Cipher -# Tests adapted from `problem-specifications//canonical-data.json` @ v1.2.0 +# Tests adapted from `problem-specifications//canonical-data.json` @ v2.0.0 class SimpleCipherTest(unittest.TestCase): # Utility functions @@ -65,10 +65,14 @@ def test_can_wrap_on_decode(self): cipher = Cipher('abcdefghij') self.assertEqual(cipher.decode('zabcdefghi'), 'zzzzzzzzzz') - def test_can_handle_messages_longer_than_key(self): + def test_can_encode_messages_longer_than_key(self): cipher = Cipher('abc') self.assertEqual(cipher.encode('iamapandabear'), 'iboaqcnecbfcr') + def test_can_decode_messages_longer_than_key(self): + cipher = Cipher('abc') + self.assertEqual(cipher.decode('iboaqcnecbfcr'), 'iamapandabear') + if __name__ == '__main__': unittest.main()