Repository: exercism/python
Pull request #505 (2017-08-03 15:59:14)

Pin pyflakes to latest version 1.6.0

pyflakes is not pinned to a specific version.

I'm pinning it to the latest version **1.6.0** for now.


These links might come in handy:  <a href="https://pypi.python.org/pypi/pyflakes">PyPI</a> | <a href="https://pyup.io/changelogs/pyflakes/">Changelog</a> | <a href="https://github.com/PyCQA/pyflakes">Repo</a> 



### Changelog
> 
>### 1.6.0

>  - Process function scope variable annotations for used names
>  - Find Python files without extensions by their shebang



>### 1.5.0

>  - Enable support for PEP 526 annotated assignments



>### 1.4.0

>  - Change formatting of ImportStarMessage to be consistent with other errors
>  - Support PEP 498 &quot;f-strings&quot;



>### 1.3.0

>  - Fix PyPy2 Windows IntegrationTests
>  - Check for duplicate dictionary keys
>  - Fix TestMain tests on Windows
>  - Fix &quot;continue&quot; and &quot;break&quot; checks ignoring py3.5&#39;s &quot;async for&quot; loop



>### 1.2.3

>  - Fix TypeError when processing relative imports



>### 1.2.2

>  - Avoid traceback when exception is del-ed in except



>### 1.2.1

>  - Fix false RedefinedWhileUnused for submodule imports



>### 1.2.0

>  - Warn against reusing exception names after the except: block on Python 3
>  - Improve the error messages for imports



>### 1.1.0

>  - Allow main() to accept arguments.
>  - Support  matrix-multiplication operator
>  - Validate __future__ imports
>  - Fix doctest scope testing
>  - Warn for tuple assertions which are always true
>  - Warn for &quot;import *&quot; not at module level on Python 3
>  - Catch many more kinds of SyntaxErrors
>  - Check PEP 498 f-strings
>  - (and a few more sundry bugfixes)



>### 1.0.0

>  - Python 3.5 support. async/await statements in particular.
>  - Fix test_api.py on Windows.
>  - Eliminate a false UnusedImport warning when the name has been
>    declared &quot;global&quot;



>### 0.9.2

>  - Fix a traceback when a global is defined in one scope, and used in another.



>### 0.9.1

>  - Update NEWS.txt to include 0.9.0, which had been forgotten.



>### 0.9.0

>  - Exit gracefully, not with a traceback, on SIGINT and SIGPIPE.
>  - Fix incorrect report of undefined name when using lambda expressions in
>    generator expressions.
>  - Don&#39;t crash on DOS line endings on Windows and Python 2.6.
>  - Don&#39;t report an undefined name if the &#39;del&#39; which caused a name to become
>    undefined is only conditionally executed.
>  - Properly handle differences in list comprehension scope in Python 3.
>  - Improve handling of edge cases around &#39;global&#39; defined variables.
>  - Report an error for &#39;return&#39; outside a function.



>### 0.8.1

>  - Detect the declared encoding in Python 3.
>  - Do not report redefinition of import in a local scope, if the
>    global name is used elsewhere in the module.
>  - Catch undefined variable in loop generator when it is also used as
>    loop variable.
>  - Report undefined name for `(a, b) = (1, 2)` but not for the general
>    unpacking `(a, b) = func()`.
>  - Correctly detect when an imported module is used in default arguments
>    of a method, when the method and the module use the same name.
>  - Distribute a universal wheel file.



>### 0.8.0

>  - Adapt for the AST in Python 3.4.
>  - Fix caret position on SyntaxError.
>  - Fix crash on Python 2.x with some doctest SyntaxError.
>  - Add tox.ini.
>  - The `PYFLAKES_NODOCTEST` environment variable has been replaced with the
>    `PYFLAKES_DOCTEST` environment variable (with the opposite meaning).
>    Doctest checking is now disabled by default; set the environment variable
>    to enable it.
>  - Correctly parse incremental `__all__ += [...]`.
>  - Catch return with arguments inside a generator (Python &lt;= 3.2).
>  - Do not complain about `_` in doctests.
>  - Drop deprecated methods `pushFunctionScope` and `pushClassScope`.



>### 0.7.3

>  - Do not report undefined name for generator expression and dict or
>    set comprehension at class level.
>  - Deprecate `Checker.pushFunctionScope` and `Checker.pushClassScope`:
>    use `Checker.pushScope` instead.
>  - Remove dependency on Unittest2 for the tests.



>### 0.7.2

>  - Fix computation of `DoctestSyntaxError.lineno` and `col`.
>  - Add boolean attribute `Checker.withDoctest` to ignore doctests.
>  - If environment variable `PYFLAKES_NODOCTEST` is set, skip doctests.
>  - Environment variable `PYFLAKES_BUILTINS` accepts a comma-separated
>    list of additional built-in names.



>### 0.7.1

>  - File `bin/pyflakes` was missing in tarball generated with distribute.
>  - Fix reporting errors in non-ASCII filenames (Python 2.x).



>### 0.7.0

>  - Add --version and --help options.
>  - Support `python -m pyflakes` (Python 2.7 and Python 3.x).
>  - Add attribute `Message.col` to report column offset.
>  - Do not report redefinition of variable for a variable used in a list
>    comprehension in a conditional.
>  - Do not report redefinition of variable for generator expressions and
>    set or dict comprehensions.
>  - Do not report undefined name when the code is protected with a
>    `NameError` exception handler.
>  - Do not report redefinition of variable when unassigning a module imported
>    for its side-effect.
>  - Support special locals like `__tracebackhide__` for py.test.
>  - Support checking doctests.
>  - Fix issue with Turkish locale where `&#39;i&#39;.upper() == &#39;i&#39;` in Python 2.



>### 0.6.1

>  - Fix detection of variables in augmented assignments.



>### 0.6.0

>  - Support Python 3 up to 3.3, based on the pyflakes3k project.
>  - Preserve compatibility with Python 2.5 and all recent versions of Python.
>  - Support custom reporters in addition to the default Reporter.
>  - Allow function redefinition for modern property construction via
>    property.setter/deleter.
>  - Fix spurious redefinition warnings in conditionals.
>  - Do not report undefined name in __all__ if import * is used.
>  - Add WindowsError as a known built-in name on all platforms.
>  - Support specifying additional built-ins in the `Checker` constructor.
>  - Don&#39;t issue Unused Variable warning when using locals() in current scope.
>  - Handle problems with the encoding of source files.
>  - Remove dependency on Twisted for the tests.
>  - Support `python setup.py test` and `python setup.py develop`.
>  - Create script using setuptools `entry_points` to support all platforms,
>    including Windows.



>### 0.5.0

>  - Convert pyflakes to use newer _ast infrastructure rather than compiler.
>  - Support for new syntax in 2.7 (including set literals, set comprehensions,
>    and dictionary comprehensions).
>  - Make sure class names don&#39;t get bound until after class definition.



>### 0.4.0

>  - Fix reporting for certain SyntaxErrors which lack line number
>    information.
>  - Check for syntax errors more rigorously.
>  - Support checking names used with the class decorator syntax in versions
>    of Python which have it.
>  - Detect local variables which are bound but never used.
>  - Handle permission errors when trying to read source files.
>  - Handle problems with the encoding of source files.
>  - Support importing dotted names so as not to incorrectly report them as
>    redefined unused names.
>  - Support all forms of the with statement.
>  - Consider static `__all__` definitions and avoid reporting unused names
>    if the names are listed there.
>  - Fix incorrect checking of class names with respect to the names of their
>    bases in the class statement.
>  - Support the `__path__` global in `__init__.py`.



>### 0.3.0

>  - Display more informative SyntaxError messages.
>  - Don&#39;t hang flymake with unmatched triple quotes (only report a single
>    line of source for a multiline syntax error).
>  - Recognize __builtins__ as a defined name.
>  - Improve pyflakes support for python versions 2.3-2.5
>  - Support for if-else expressions and with statements.
>  - Warn instead of error on non-existent file paths.
>  - Check for __future__ imports after other statements.
>  - Add reporting for some types of import shadowing.
>  - Improve reporting of unbound locals






*Got merge conflicts? Close this PR and delete the branch. I'll create a new PR for you.*

Happy merging! 🤖



Patch:
diff --git a/.gitignore b/.gitignore
index 013c3a08..98971d3a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,4 @@
 tmp
 bin/configlet
 bin/configlet.exe
+.idea/
diff --git a/config.json b/config.json
index 2122329c..024a11e6 100644
--- a/config.json
+++ b/config.json
@@ -801,6 +801,28 @@
         "Algorithms"
       ]
     },
+    {
+      "uuid": "bacb2184-025f-0980-38d6-34fb419e03a6d3d8b44",
+      "slug": "two-fer",
+      "core": false,
+      "unlocked_by": null,
+      "difficulty": 1,
+      "topics": [
+        "control-flow (if-else statements)",
+        "optional values",
+        "text formatting"
+      ]
+    },
+    {
+      "uuid": "33f689ee-1d9c-4908-a71c-f84bff3510df",
+      "slug": "collatz-conjecture",
+      "core": false,
+      "unlocked_by": null,
+      "difficulty": 1,
+      "topics": [
+            "control-flow (loops)"
+      ]
+    },
     {
       "uuid": "e7351e8e-d3ff-4621-b818-cd55cf05bffd",
       "slug": "accumulate",
diff --git a/config/maintainers.json b/config/maintainers.json
new file mode 100644
index 00000000..2a146873
--- /dev/null
+++ b/config/maintainers.json
@@ -0,0 +1,25 @@
+{
+  "maintainers": [
+    {
+      "github_username": "behrtam",
+      "show_on_website": false,
+      "alumnus": false,
+      "name": null,
+      "bio": null,
+      "link_text": null,
+      "link_url": null,
+      "avatar_url": null
+    },
+    {
+      "github_username": "Dog",
+      "show_on_website": false,
+      "alumnus": false,
+      "name": null,
+      "bio": null,
+      "link_text": null,
+      "link_url": null,
+      "avatar_url": null
+    }
+  ],
+  "docs_url": "https://github.com/exercism/docs/blob/master/maintaining-a-track/maintainer-configuration.md"
+}
diff --git a/docs/ABOUT.md b/docs/ABOUT.md
index e0ab9378..a9d54b2c 100644
--- a/docs/ABOUT.md
+++ b/docs/ABOUT.md
@@ -1,7 +1,15 @@
-Python is a strong language for beginners. There are many resources available for programmers of all levels, the code is highly readable, and in many cases phrases are comparable to those in the English language. Code can be written and executed from the command line, in an interactive IPython session, or in a [Jupyter](http://jupyter.org) (IPython) notebook.
+Python is a strong language for beginners. 
+There are many resources available for programmers of all levels, the code is highly readable, and in many cases phrases are comparable to those in the English language.
+Code can be written and executed from the command line, in an interactive IPython session, or in a [Jupyter](http://jupyter.org) (IPython) notebook.
 
-The most common form of Python is compiled in C. This is often invisible to the beginning programmer, but if there are uses for which exceptionally fast implementation is needed then C extensions can be written to optimize Python execution.
+The most common form of Python is compiled in C.
+This is often invisible to the beginning programmer, but if there are uses for which exceptionally fast implementation is needed then C extensions can be written to optimize Python execution.
 
 [Python is used extensively](https://www.python.org/about/apps/) in scientific computing, finance, games, networking, internet development, and in assembling pipelines of other programs.
 
-Python was started by Guido van Rossum in 1989. Its name is an homage to the comedy troupe Monty Python. Python 2 is used widely but support may end by 2020. Python 3 was introduced in 2008 and is beginning to be adopted more widely. They are similar, but users will encounter [some differences](http://blog.teamtreehouse.com/python-2-vs-python-3). Python development is shepherded by [The Python Software Foundation](https://www.python.org/about/) and there are active community-based user groups worldwide.
+Python was started by Guido van Rossum in 1989.
+Its name is an homage to the comedy troupe Monty Python. 
+Python 2 is used widely but support may end by 2020.
+Python 3 was introduced in 2008 and is beginning to be adopted more widely.
+They are similar, but users will encounter [some differences](http://blog.teamtreehouse.com/python-2-vs-python-3). 
+Python development is shepherded by [The Python Software Foundation](https://www.python.org/about/) and there are active community-based user groups worldwide.
\ No newline at end of file
diff --git a/exercises/accumulate/accumulate.py b/exercises/accumulate/accumulate.py
index 741fb58b..d67a51b0 100644
--- a/exercises/accumulate/accumulate.py
+++ b/exercises/accumulate/accumulate.py
@@ -1,2 +1,2 @@
-def accumulate():
+def accumulate(array, function):
     pass
diff --git a/exercises/allergies/allergies.py b/exercises/allergies/allergies.py
index 9c8934ed..6caab68b 100644
--- a/exercises/allergies/allergies.py
+++ b/exercises/allergies/allergies.py
@@ -1,3 +1,11 @@
 class Allergies(object):
-    def __init__(self):
+
+    def __init__(self, number):
+        pass
+
+    def is_allergic_to(self, string):
+        pass
+
+    @property
+    def lst(self):
         pass
diff --git a/exercises/alphametics/alphametics.py b/exercises/alphametics/alphametics.py
index 0415ad7a..035cc1e5 100644
--- a/exercises/alphametics/alphametics.py
+++ b/exercises/alphametics/alphametics.py
@@ -1,2 +1,2 @@
-def solve():
+def solve(string):
     pass
diff --git a/exercises/anagram/anagram.py b/exercises/anagram/anagram.py
index c23ccd71..7b6b3aa0 100644
--- a/exercises/anagram/anagram.py
+++ b/exercises/anagram/anagram.py
@@ -1,2 +1,2 @@
-def detect_anagrams():
+def detect_anagrams(string, array):
     pass
diff --git a/exercises/atbash-cipher/atbash_cipher.py b/exercises/atbash-cipher/atbash_cipher.py
index 2536c2e6..1a673ebe 100644
--- a/exercises/atbash-cipher/atbash_cipher.py
+++ b/exercises/atbash-cipher/atbash_cipher.py
@@ -1,6 +1,6 @@
-def encode():
+def encode(string):
     pass
 
 
-def decode():
+def decode(string):
     pass
diff --git a/exercises/beer-song/beer_song.py b/exercises/beer-song/beer_song.py
index 549c5bb9..3be70421 100644
--- a/exercises/beer-song/beer_song.py
+++ b/exercises/beer-song/beer_song.py
@@ -1,6 +1,6 @@
-def verse():
+def verse(number):
     pass
 
 
-def song():
+def song(number1, number2=0):
     pass
diff --git a/exercises/binary-search/binary_search.py b/exercises/binary-search/binary_search.py
index 19e630d5..6ec933da 100644
--- a/exercises/binary-search/binary_search.py
+++ b/exercises/binary-search/binary_search.py
@@ -1,2 +1,2 @@
-def binary_search():
+def binary_search(array, number):
     pass
diff --git a/exercises/binary/binary.py b/exercises/binary/binary.py
index e1f4ca6c..49cee190 100644
--- a/exercises/binary/binary.py
+++ b/exercises/binary/binary.py
@@ -1,2 +1,2 @@
-def parse_binary():
+def parse_binary(string):
     pass
diff --git a/exercises/bob/bob.py b/exercises/bob/bob.py
index 5221e0ec..a2bdda64 100644
--- a/exercises/bob/bob.py
+++ b/exercises/bob/bob.py
@@ -1,2 +1,2 @@
-def hey():
+def hey(string):
     pass
diff --git a/exercises/book-store/book_store.py b/exercises/book-store/book_store.py
index a9feac68..711be23f 100644
--- a/exercises/book-store/book_store.py
+++ b/exercises/book-store/book_store.py
@@ -1,2 +1,2 @@
-def calculate_total():
+def calculate_total(array, number=None):
     pass
diff --git a/exercises/bracket-push/bracket_push.py b/exercises/bracket-push/bracket_push.py
index f5e2d19f..da11127d 100644
--- a/exercises/bracket-push/bracket_push.py
+++ b/exercises/bracket-push/bracket_push.py
@@ -1,2 +1,2 @@
-def check_brackets():
+def check_brackets(string):
     pass
diff --git a/exercises/collatz-conjecture/README.md b/exercises/collatz-conjecture/README.md
new file mode 100644
index 00000000..6b2ede8b
--- /dev/null
+++ b/exercises/collatz-conjecture/README.md
@@ -0,0 +1,61 @@
+# Collatz Conjecture
+
+The Collatz Conjecture or 3x+1 problem can be summarized as follows:
+
+Take any positive integer n. If n is even, divide n by 2 to get n / 2. If n is
+odd, multiply n by 3 and add 1 to get 3n + 1. Repeat the process indefinitely.
+The conjecture states that no matter which number you start with, you will
+always reach 1 eventually.
+
+Given a number n, return the number of steps required to reach 1.
+
+(Keep in mind that the Conjecture is only concerned with strictly positive
+integers, so your solution should return `None` if given 0 or a negative
+integer.)
+
+## Examples
+
+Starting with n = 3, the steps would be as follows:
+
+0. 3
+1. 10
+2. 5
+3. 16
+4. 8
+5. 4
+6. 2
+7. 1
+
+Resulting in 7 steps. So for input n = 3, the return value would be 7.
+
+Starting with n = 12, the steps would be as follows:
+
+0. 12
+1. 6
+2. 3
+3. 10
+4. 5
+5. 16
+6. 8
+7. 4
+8. 2
+9. 1
+
+Resulting in 9 steps. So for input n = 12, the return value would be 9.
+
+### Submitting Exercises
+
+Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
+
+For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
+
+
+For more detailed information about running tests, code style and linting,
+please see the [help page](http://exercism.io/languages/python).
+
+## Source
+
+An unsolved problem in mathematics named after mathematician Lothar Collatz [https://en.wikipedia.org/wiki/3x_%2B_1_problem](https://en.wikipedia.org/wiki/3x_%2B_1_problem)
+
+## 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/collatz-conjecture/collatz_conjecture.py b/exercises/collatz-conjecture/collatz_conjecture.py
new file mode 100644
index 00000000..5378d4d9
--- /dev/null
+++ b/exercises/collatz-conjecture/collatz_conjecture.py
@@ -0,0 +1,2 @@
+def collatz_steps():
+    pass
diff --git a/exercises/collatz-conjecture/example.py b/exercises/collatz-conjecture/example.py
new file mode 100644
index 00000000..024a5f73
--- /dev/null
+++ b/exercises/collatz-conjecture/example.py
@@ -0,0 +1,17 @@
+def collatz_steps(n):
+    if n <= 0:
+        return
+
+    step_count = 0
+    while n > 1:
+        if is_odd(n):
+            n = n * 3 + 1
+        else:
+            n = n / 2
+        step_count += 1
+
+    return step_count
+
+
+def is_odd(n):
+    return n % 2 == 1
diff --git a/exercises/isogram/isogram.py b/exercises/isogram/isogram.py
index 7b0ccf92..98b31c5b 100644
--- a/exercises/isogram/isogram.py
+++ b/exercises/isogram/isogram.py
@@ -1,2 +1,2 @@
-def is_isogram():
+def is_isogram(string):
     pass
diff --git a/exercises/saddle-points/README.md b/exercises/saddle-points/README.md
index 09673f5c..7f933046 100644
--- a/exercises/saddle-points/README.md
+++ b/exercises/saddle-points/README.md
@@ -15,7 +15,7 @@ So say you have a matrix like so:
 It has a saddle point at (1, 0).
 
 It's called a "saddle point" because it is greater than or equal to
-every element in its row and the less than or equal to every element in
+every element in its row and less than or equal to every element in
 its column.
 
 A matrix may have zero or more saddle points.
diff --git a/exercises/scale-generator/example.py b/exercises/scale-generator/example.py
index b836b64a..5e39a85e 100644
--- a/exercises/scale-generator/example.py
+++ b/exercises/scale-generator/example.py
@@ -26,6 +26,8 @@ class Scale(object):
         for i, interval in enumerate(self.pattern):
             pitches.append(scale[last_index])
             last_index += self.ASCENDING_INTERVALS.index(interval) + 1
+        if pitches[0] != scale[last_index % len(scale)]:
+            raise ValueError()
         return pitches
 
     def _reorder_chromatic_scale(self):
diff --git a/exercises/two-fer/README.md b/exercises/two-fer/README.md
new file mode 100644
index 00000000..22d56648
--- /dev/null
+++ b/exercises/two-fer/README.md
@@ -0,0 +1,29 @@
+# Two-fer
+
+`Two-fer` or `2-fer` is short for two for one. One for you and one for me.
+
+```
+"One for X, one for me."
+```
+
+When X is a name or "you".
+
+If the given name is "Alice", the result should be "One for Alice, one for me."
+If no name is given, the result should be "One for you, one for me."
+
+### Submitting Exercises
+
+Note that, when trying to submit an exercise, make sure the solution is in the `exercism/python/<exerciseName>` directory.
+
+For example, if you're submitting `bob.py` for the Bob exercise, the submit command would be something like `exercism submit <path_to_exercism_dir>/python/bob/bob.py`.
+
+
+For more detailed information about running tests, code style and linting,
+please see the [help page](http://exercism.io/languages/python).
+
+## Source
+
+This is an exercise to introduce users to basic programming constructs, just after hello World. [https://en.wikipedia.org/wiki/Two-fer](https://en.wikipedia.org/wiki/Two-fer)
+
+## Submitting Incomplete Solutions
+It's possible to submit an incomplete solution so you can see how others have completed the exercise.
\ No newline at end of file
diff --git a/exercises/two-fer/example.py b/exercises/two-fer/example.py
new file mode 100644
index 00000000..be9c6c5c
--- /dev/null
+++ b/exercises/two-fer/example.py
@@ -0,0 +1,5 @@
+def two_fer(name=""):
+    if not name.strip():
+        return "One for you, one for me."
+    else:
+        return "One for %s, one for me." % name
diff --git a/exercises/two-fer/two_fer.py b/exercises/two-fer/two_fer.py
new file mode 100644
index 00000000..e75f4e56
--- /dev/null
+++ b/exercises/two-fer/two_fer.py
@@ -0,0 +1,2 @@
+def two_fer():
+    pass
diff --git a/requirements-travis.txt b/requirements-travis.txt
index ed405889..177bc351 100644
--- a/requirements-travis.txt
+++ b/requirements-travis.txt
@@ -1,3 +1,3 @@
 flake8==3.4.1
 pep8>=1.7,<1.7.99
-pyflakes>=1.5,<1.5.99
+pyflakes==1.6.0


Test patch:
diff --git a/exercises/change/change_test.py b/exercises/change/change_test.py
index 3f893633..44e4f914 100644
--- a/exercises/change/change_test.py
+++ b/exercises/change/change_test.py
@@ -19,13 +19,18 @@ class ChangeTest(unittest.TestCase):
                          [21, 21, 21])
 
     def test_large_target_values(self):
-        self.assertEqual(find_minimum_coins(21, [2, 5, 10, 20, 50]),
-                         [2, 2, 2, 5, 10])
+        self.assertEqual(find_minimum_coins(999, [1, 2, 5, 10, 20, 50, 100]),
+                         [2, 2, 5, 20, 20, 50, 100, 100, 100,
+                          100, 100, 100, 100, 100, 100])
 
     def test_possible_change_without_unit_coins_available(self):
         self.assertEqual(find_minimum_coins(21, [2, 5, 10, 20, 50]),
                          [2, 2, 2, 5, 10])
 
+    def test_another_possible_change_without_unit_coins_available(self):
+        self.assertEqual(find_minimum_coins(27, [4, 5]),
+                         [4, 4, 4, 5, 5, 5])
+
     def test_no_coins_make_0_change(self):
         self.assertEqual(find_minimum_coins(0, [1, 5, 10, 21, 25]), [])
 
diff --git a/exercises/collatz-conjecture/collatz_conjecture_test.py b/exercises/collatz-conjecture/collatz_conjecture_test.py
new file mode 100644
index 00000000..9e30be11
--- /dev/null
+++ b/exercises/collatz-conjecture/collatz_conjecture_test.py
@@ -0,0 +1,32 @@
+import unittest
+
+from collatz_conjecture import collatz_steps
+
+# Test cases adapted from `x-common//canonical-data.json` @ version: 1.1.1
+
+
+class CollatzConjectureTests(unittest.TestCase):
+
+    def test_zero_steps_for_one(self):
+        self.assertEqual(collatz_steps(1), 0)
+
+    def test_divide_if_even(self):
+        self.assertEqual(collatz_steps(16), 4)
+
+    def test_even_and_odd_steps(self):
+        self.assertEqual(collatz_steps(12), 9)
+
+    def test_large_number_of_even_and_odd_steps(self):
+        self.assertEqual(collatz_steps(1000000), 152)
+
+    def test_zero_is_invalid_input(self):
+        self.assertEqual(collatz_steps(0), None)
+
+    def test_negative_number_is_invalid_input(self):
+        self.assertEqual(collatz_steps(-1), None)
+
+        self.assertEqual(collatz_steps(-15), None)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/exercises/isogram/isogram_test.py b/exercises/isogram/isogram_test.py
index 2459ae09..6573f898 100644
--- a/exercises/isogram/isogram_test.py
+++ b/exercises/isogram/isogram_test.py
@@ -34,6 +34,9 @@ class TestIsogram(unittest.TestCase):
     def test_duplicated_character_in_the_middle(self):
         self.assertFalse(is_isogram("accentor"))
 
+    def test_isogram_with_duplicated_letter_and_nonletter_character(self):
+        self.assertFalse(is_isogram("Aleph Bot Chap"))
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/exercises/scale-generator/scale_generator_test.py b/exercises/scale-generator/scale_generator_test.py
index 5031e031..a1825faa 100644
--- a/exercises/scale-generator/scale_generator_test.py
+++ b/exercises/scale-generator/scale_generator_test.py
@@ -115,6 +115,10 @@ class ScaleGeneratorTest(unittest.TestCase):
         actual = enigmatic.pitches
         self.assertEqual(expected, actual)
 
+    def test_brokeninterval(self):
+        with self.assertRaises(ValueError):
+            Scale('G', 'enigmatic', 'mAMMMmM')
+
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/exercises/two-fer/two_fer_test.py b/exercises/two-fer/two_fer_test.py
new file mode 100644
index 00000000..ceef22c2
--- /dev/null
+++ b/exercises/two-fer/two_fer_test.py
@@ -0,0 +1,18 @@
+import unittest
+
+import two_fer
+
+
+class Two_Fer_test(unittest.TestCase):
+    def test_empty(self):
+        self.assertEqual(two_fer.two_fer(), 'One for you, one for me.')
+
+    def test_eve(self):
+        self.assertEqual(two_fer.two_fer("Eve"), "One for Eve, one for me.")
+
+    def test_bob(self):
+        self.assertEqual(two_fer.two_fer("Bob"), "One for Bob, one for me.")
+
+
+if __name__ == '__main__':
+    unittest.main()