--- /dev/null
+Licenses
+========
+
+Instructional Material
+----------------------
+
+All Software Carpentry instructional material is made available under
+the Creative Commons Attribution license. You are free:
+
+* to **Share**---to copy, distribute and transmit the work
+* to **Remix**---to adapt the work
+
+Under the following conditions:
+
+* **Attribution**---You must attribute the work using "Copyright (c)
+ Software Carpentry" (but not in any way that suggests that we
+ endorse you or your use of the work). Where practical, you must
+ also include a hyperlink to http://software-carpentry.org.
+
+With the understanding that:
+
+* **Waiver**---Any of the above conditions can be waived if you get
+ permission from the copyright holder.
+* **Other Rights**---In no way are any of the following rights
+ affected by the license:
+ * Your fair dealing or fair use rights;
+ * The author's moral rights;
+ * Rights other persons may have either in the work itself or in
+ how the work is used, such as publicity or privacy rights. *
+* **Notice**---For any reuse or distribution, you must make clear to
+ others the license terms of this work. The best way to do this is
+ with a link to
+ [http://creativecommons.org/licenses/by/3.0/](http://creativecommons.org/licenses/by/3.0/).
+
+For the full legal text of this license, please see
+[http://creativecommons.org/licenses/by/3.0/legalcode](http://creativecommons.org/licenses/by/3.0/legalcode).
+
+Software
+--------
+
+Except where otherwise noted, the example programs and other software
+provided by Software Carpentry are made available under the
+[OSI](http://opensource.org)-approved
+[MIT license](http://opensource.org/licenses/mit-license.html).
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Trademark
+---------
+
+"Software Carpentry" and the Software Carpentry logo are registered
+trademarks of Software Carpentry, Ltd.
--- /dev/null
+"""
+ears.py : a simple unit testing library for teaching in the IPython Notebook.
+
+ears.run() looks for all the functions defined in the calling stack frame
+(hopefully the top level interpreter session) whose names begin with the
+characters 'test_' and calls them in an undetermined order, collecting and
+reporting results.
+
+Usage:
+
+ import ears
+
+ def test_pass(): pass
+ def test_fail(): assert False, 'Error message'
+ def test_error(): 1/0 # zero division error
+
+ ears.run()
+"""
+
+import sys
+import inspect
+import traceback
+
+def run():
+ """
+ Look for test functions defined by caller, execute, and report.
+ """
+ # Collect functions defined in calling context.
+ caller_defs = inspect.stack()[1][0].f_globals
+ test_functions = dict([(n, caller_defs[n]) for n in caller_defs
+ if n.startswith('test_') and callable(caller_defs[n])])
+ setup = caller_defs.get('setup', None)
+ teardown = caller_defs.get('teardown', None)
+
+ # Execute and record.
+ passes = []
+ fails = []
+ errors = []
+ for (name, test) in test_functions.iteritems():
+ if setup is not None:
+ setup()
+ try:
+ test()
+ passes.append((name, None))
+ sys.stdout.write('.')
+ except AssertionError as e:
+ fails.append((name, traceback.format_exc()))
+ sys.stdout.write('f')
+ except Exception as e:
+ errors.append((name, traceback.format_exc()))
+ sys.stdout.write('E')
+ if teardown is not None:
+ teardown()
+
+ # Report.
+ print
+ print '{0} pass, {1} fail, {2} error'.format(len(passes),
+ len(fails),
+ len(errors))
+ for (title, group) in (('fail', fails),
+ ('error', errors)):
+ for (name, exc) in group:
+ print '{0}\n{1}: {2}'.format('-'*40, title, name)
+ print exc
+
+if __name__ == '__main__':
+
+ def test_pass():
+ pass
+
+ def test_fail():
+ assert False, 'Error message'
+
+ def test_error():
+ 1/0
+
+ run()