diff --git a/docs/background/community.rst b/docs/background/community.rst index 5ed985502..c28eac2ac 100644 --- a/docs/background/community.rst +++ b/docs/background/community.rst @@ -36,7 +36,7 @@ want to contribute code, please `fork the code`_ and `submit a pull request`_. .. _announcement: https://groups.google.com/forum/#!topic/beeware-users/iUm-EDgypTg .. _BeeWare Users: https://groups.google.com/forum/#!forum/beeware-users .. _BeeWare Developers: https://groups.google.com/forum/#!forum/beeware-developers -.. _log them on Github: https://github.com/beeware/batavia/issues +.. _log them on GitHub: https://github.com/beeware/batavia/issues .. _fork the code: https://github.com/beeware/batavia .. _submit a pull request: https://github.com/beeware/batavia/pulls diff --git a/docs/how-to/builtins.rst b/docs/how-to/builtins.rst index 1403a4e35..98d37df9d 100644 --- a/docs/how-to/builtins.rst +++ b/docs/how-to/builtins.rst @@ -1,104 +1,155 @@ Implementing Python Built-ins in JavaScript =========================================== +Python's builtins give Python its trademark magical feel. If you're new to Python, +please read up on the builtins_. + +Most builtins have already been added to the project, but many are do not quite match the original +implementation exactly. Some may not handle certain types of inputs correctly. In addition, new builtins_ +may arrive with the latest and greatest Python version. This guide should serve as your field manual for +adding, updating, and navigating our implementations. + +Process +------- + +The first thing to do when adding anything to Batavia is to play around a bit with it in a +`Python REPL `_. +Here's an example using ``max()``:: + + >> max([1]) + 1 + >> max((1, 2, 3, 4)) + 4 + >> max(4) + Traceback (most recent call last): + File "", line 1, in + TypeError: 'int' object is not iterable + +Your goal is to find out how the function responds to various inputs and outputs. +You may also want to consult the official documentation of the builtins_. +Once you're a little familiar, you can start to add your implementation to Batavia. + General Structure ------------------ +***************** -JavaScript versions of Python built-in functions can be found inside the ``batavia/builtins`` -directory in the Batavia code. Each built-in is placed inside its own file. +`JavaScript `_ +versions of Python built-in functions can be found inside the ``batavia/builtins`` +directory in the Batavia code. Each built-in is placed inside its own file. These builtins are +designed to be used only inside Batavia, as such they need to ensure they are being used in +a compatible manner. -.. code-block:: javascript +Each builtin function will receive arguments and keyword arguments and needs to handle them, +even if the result is throwing an error. Args should be an array, and kwargs should be a +JavaScript `object `_. +The first thing to do is check that both were passed in. + +Let's take a look at an example using the ``max()`` builtin. - // Example: a function that accepts exactly one argument, and no keyword arguments +Note: ``max()`` already exists in ``batavia/builtins/max.js``, though +if you can make any improvements or find any bugs to fix, they are welcome. + +.. code-block:: javascript - var = function(, ) { - // These builtins are designed to be used only inside Batavia, as such they need to ensure - // they are being used in a compatible manner. + // Max returns the largest item in an iterable or + // the largest of two or more arguments. - // Batavia will only ever pass two arguments, args and kwargs. If more or fewer arguments - // are passed, then Batavia is being used in an incompatible way. - // See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/arguments + function max(args, kwargs) { + // Always add this code. if (arguments.length !== 2) { throw new builtins.BataviaError.$pyclass("Batavia calling convention not used."); } - // We are now checking if a kwargs object is passed. If it isn't kwargs will be null. Like - // obj.keys() in Python we can use Object.keys(obj) to get the keys of an object. If the - // function doesn't need support any kwargs we throw an error. - if (kwargs && Object.keys(kwargs).length > 0) { - throw new builtins.TypeError.$pyclass("() doesn't accept keyword arguments."); - } +This code ensures that the function can handle keyword arguments. +Next, we need to validate the arguments are correct. We can use JavaScript's +`Object.keys() `_ +to get the keys of an object. If we can't accept certain +args or kwargs, we will check the Python REPL to see what kind of error should be thrown and throw it. - // Now we can check if the function has the supported number of arguments. In this case a - // single required argument. - if (!args || args.length !== 1) { - throw new builtins.TypeError.$pyclass("() expected exactly 1 argument (" + args.length + " given)"); - } +.. tabs:: - // If the function only works with a specific object type, add a test - var obj = args[0]; - if (!types.isinstance(obj, types.)) { - throw new builtins.TypeError.$pyclass( - "() expects a (" + type_name(obj) + " given)"); - } + .. group-tab:: Python REPL - // actual code goes here - Javascript.Function.Stuff(); - } + .. code-block:: - .__doc__ = 'docstring from Python 3.4 goes here, for documentation' + >> max(a=1) + TypeError: max expected 1 arguments, got 0 + >> max(True) + TypeError: 'bool' object is not iterable + >> max([]) + ValueError: max() arg is an empty sequence - modules.export = + .. group-tab:: Batavia Code + .. code-block:: javascript -Adding Tests ------------- + if (!args || args.length === 0) { + throw new exceptions.TypeError.$pyclass('max expected 1 arguments, got ' + args.length) + } -The tests corresponding to Batavia implementations of built-ins are available inside -``tests/builtins``. The Batavia test infrastructure includes a system to check the compatibility of -JavaScript implementation of Python with the reference CPython implementation. + if (args.length > 1) { + ... + } else { + if (!args[0].__iter__) { + throw new exceptions.TypeError.$pyclass("'" + type_name(args[0]) + "' object is not iterable") + } + } -It does this by running a test in the Python interpreter, and then running the same code using -Batavia in the Node.js JavaScript interpreter. It will compare the output in both cases to see if -they match. Furthermore the test suite will automatically test the builtin against values of all -data types to check if it gets the same response across both implementations. + try { + ... + } catch (err) { + if (err instanceof exceptions.StopIteration.$pyclass) { + throw new exceptions.ValueError.$pyclass('max() arg is an empty sequence') + } + } -In many cases these tests will not cover everything, so you can add your own. For an example look at -the ``test_bool.py`` file in ``tests/builtins``. You will see two classes with test cases, -``BoolTests`` and ``BuiltinBoolFunctionTests``. Both derive from ``TranspileTestCase`` which -handles running your code in both interpreters and comparing outputs. + Useful functions are ``types.isinstance``, which checks for a match against a Batavia type or list, + of Batavia types, ``types.isbataviainstance``, which checks for a match against any Batavia instance, + ``Object.keys(kwargs)`` for dealing with kwargs, and JavaScript's ``for in``, ``for of``, and + ``Array.forEach`` loops for iterating over the JavaScript arrays and objects. -Let's look at some test code that checks if a the Batavia implementation of ``bool`` can handle a -bool-like class that implements ``__bool__``. + Note also the format for errors: ``throw new exceptions..$pyclass``. -.. code-block:: Python +Returning a value +***************** - def test_bool_like(self): - self.assertCodeExecution(""" - class BoolLike: - def __init__(self, val): - self.val = val +Builtins_ implement Python functions and should return a Python object. +Batavia implementations of all Python types are located in ``/batavia/types.js``. +JavaScript imports use the ``require`` keyword and can be imported inline or at +the top of the file. Inline imports can be preferable in some cases. - def __bool__(self): - return self.val == 1 - print(bool(BoolLike(0))) - print(bool(BoolLike(1))) - """) +.. code-block:: javascript -The ``assertCodeExecution`` method will run the code provided to it in both implementations. This -code needs to generate some output so that the output can be compared, hence the need to print the -values. + ... + Tuple = require('../types.js').Tuple + return new Tuple(my, results, here) + } -Process ----------- +Documentation +************* + +Finally, add the `docstring `_ +to the function object. In JavaScript, like in Python, functions +are first-class objects and can have additional properties. + +.. code-block:: javascript + + list.__doc__ = 'docstring from Python 3.x goes here, for documentation' -For a given function, run `functionname.__doc__` in the Python 3.4 repl + module.exports = list -Copy the docstring into the doc +Tests +***** -Run the function in Python 3.4 +No implementation for a project like this is complete without tests. Check out the other sections for +more details on test structure. Tests are located in ``/tests`` in a similar folder structure to the +core code, and most test files have already been created. Some things that should almost always be +tested: -Take a guess at the implementation structure based on the other functions. +* Write a test or three to ensure your function returns the correct output with some normal inputs. +* Think of a few weird inputs that could throw off your code (or future code). Test them. +* If you are throwing an error (excluding ``BataviaError``) anywhere, write a test that tries to throw it. +* If you accounted for an edge case (look for an ``if`` statement), test it. +* Check out the `official documentation `_ for more edge cases. -Copy the style of the other implemented functions +.. _builtins: https://docs.python.org/3/library/functions.html diff --git a/docs/how-to/contribute-code.rst b/docs/how-to/contribute-code.rst index 8fb9c6153..9442b2768 100644 --- a/docs/how-to/contribute-code.rst +++ b/docs/how-to/contribute-code.rst @@ -2,7 +2,7 @@ Contributing to Batavia's code ============================== In the following instructions, we're going to assume you’re familiar with -Github and making pull requests. We're also going to assume some entry level +GitHub and making pull requests. We're also going to assume some entry level Python and JavaScript; if anything we describe here doesn’t make sense, don’t worry - we're more than happy to fill in the gaps. At this point, we don’t know what you don’t know! diff --git a/docs/how-to/contribute-docs.rst b/docs/how-to/contribute-docs.rst index 266320cc2..5566fdfb0 100644 --- a/docs/how-to/contribute-docs.rst +++ b/docs/how-to/contribute-docs.rst @@ -32,5 +32,6 @@ Create the static files: :: $ make html -Check for any errors and,if possible, fix them. The output of the file should -be in the ``_build/html`` folder. Open the file you changed in the browser. +Check for any errors and, if possible, fix them. +The output of the file should be in the ``_build/html`` folder. +Open the file you changed in the browser. diff --git a/docs/how-to/contribute-tests.rst b/docs/how-to/contribute-tests.rst new file mode 100644 index 000000000..836c45cfc --- /dev/null +++ b/docs/how-to/contribute-tests.rst @@ -0,0 +1,123 @@ +Implementing Tests in Batavia +============================= + +Basic Test Structure +-------------------- + +Batavia's job is to run a browser-compatible Python compiler, which takes valid Python as input and runs it. +Therefore, tests should test that the output of the Batavia compiler matches the output of CPython:: + + print('Hello') # Code to test + Hello # CPython output + Hello # Batavia output + # Outputs match. Test passes! + +This test structure is simple and effective. It's used in almost every test we've written. + +Adding Tests +------------ + +In many cases, existing tests will not cover everything. Feel free to add your own! + +The tests corresponding to Batavia implementations of built-ins are available inside +``tests/builtins``. The Batavia test infrastructure includes a system to check the compatibility of +JavaScript implementation of Python with the reference CPython implementation. + +These tests all derive from ``TranspileTestCase``, which handles running your code in both interpreters +and comparing outputs. For an example, look at the ``test_bool.py`` file in ``tests/builtins``. You +will see two classes with test cases, ``BoolTests`` and ``BuiltinBoolFunctionTests``. Both derive +from ``TranspileTestCase``. + +Let's look at some test code that checks if a the Batavia implementation of ``bool`` can handle a +bool-like class that implements ``__bool__``. + +.. code-block:: Python + + def test_bool_like(self): + self.assertCodeExecution(""" + class BoolLike: + def __init__(self, val): + self.val = val + + def __bool__(self): + return self.val == 1 + print(bool(BoolLike(0))) + print(bool(BoolLike(1))) + """) + +The ``assertCodeExecution`` method will run the code provided to it in both implementations. This +code needs to generate some output so that the output can be compared, hence the need to print the +values. **Code that is not being printed is not being tested.** + +Finally, ``print()`` is an imperfect creature for tests. Some things in Python aren't guaranteed to +print out in the same order every time, like sets dictionaries. Tests should be structured to compensate, +for instance by converting to a sorted list. See also the output cleaners section below. + +Template +-------- + +.. code-block:: python + + def test__(self): + # Valid Python code to be tested. + code = """ + print('>>> print()') + print() + """ + self.assertCodeExecution(code) + +This code block provides a printout of the code being run as well as the output of the code, +which can be very useful for debugging in test cases where more than a few lines of code are being run. + +Testing for Errors +------------------ + +Since we're testing the compiler, we need to ensure that errors for all of the builtins are thrown correctly. +We also want to ensure that we're not getting the wrong errors in our tests. Simply include a try/except +block in your test. + +.. code-block:: python + + def test_some_error(self): + code = """ + try: + code_that_raises_a_ValueError() + except ValueError as err: + print(err) + print("Test complete!") + """ + self.assertCodeExecution(code) + +Remember to catch the specific error you want, and then print the error. Then, print a success message to +validate that your except block didn't crash as well. **Code that is not being printed is not being tested.** + +Output Cleaners +--------------- + +In some cases, the test output will vary. ``TranspileTestCase`` will automatically apply some common output +cleanup for you. Some cases will need more or less cleanup. If you run your Python code directly in the REPL, +and the output differs from the test case output, you may need to modify what cleanup steps are being run. + +As such, ``assertCodeExecution`` accepts optional ``js_cleaner`` and ``py_cleaner`` objects. These can be provided by +the ``@transform`` decorator, located in ``tests/utils/output_cleaners.py``. Here's an example: + +.. code-block:: python + + @transform(float_exp=False) + def test_some_floats(self, js_cleaner, py_cleaner): # + Cleaner objects as arguments + code = ... + self.assertCodeExecution(code, js_cleaner=js_cleaner, py_cleaner=py_cleaner) # + Cleaner objects again + +This code means that the output of floating-point numbers will not be normalized using a regex. Refer to other +test cases and the docstring for ``@transform`` for more examples. + +Node/Python Crashes +------------------- + +If the CPython or JavaScript code crashes outright, UnitTest struggles. For instance, +``confused END_FINALLY`` in the middle of your test output tends to mean that the JavaScript code threw an +uncaught exception, causing Node to stop. It's hard for UnitTest to pull the details out of this type of thing +since that error occurred in Node, not Python. + +These types of errors will often appear above the test case as a crash report instead of in the usual section for the +output of your test's print() statements. Look there for clues. diff --git a/docs/how-to/development-env.rst b/docs/how-to/development-env.rst index c3dd959f8..5475ba722 100644 --- a/docs/how-to/development-env.rst +++ b/docs/how-to/development-env.rst @@ -4,7 +4,7 @@ Setting up your development environment The process of setting up a development environment is very similar to the :doc:`/tutorial/tutorial-0` process. The biggest difference is that instead of using the official BeeWare repository, you'll be using your own -Github fork. +GitHub fork. As with the getting started guide, these instructions will assume that you have Python 3 (currently supported versions are 3.5, 3.6, and 3.7). @@ -12,7 +12,7 @@ have Python 3 (currently supported versions are 3.5, 3.6, and 3.7). Batavia codebase ---------------- -Start by forking Batavia into your own Github repository; then +Start by forking Batavia into your own GitHub repository; then check out your fork to your own computer into a development directory: .. code-block:: bash diff --git a/docs/how-to/index.rst b/docs/how-to/index.rst index e448c67ef..7171f878a 100644 --- a/docs/how-to/index.rst +++ b/docs/how-to/index.rst @@ -16,5 +16,8 @@ stand alone. development-env contribute-code contribute-docs - Implementing Python Built-ins in JavaScript - Adding a module and testing it + contribute-tests + tour + builtins + types + modules-and-tests diff --git a/docs/how-to/modules-and-tests.rst b/docs/how-to/modules-and-tests.rst index ddc5b955e..a8f4f1d2b 100644 --- a/docs/how-to/modules-and-tests.rst +++ b/docs/how-to/modules-and-tests.rst @@ -18,7 +18,8 @@ To create a test If the module doesn't exist yet, it must be created as a ``test_NAME-OF-THE-MODULE.py`` file. -If a test for the module already exists and you want to add functionalities to it, you must create a new function on the file. +If a test for the module already exists and you want to add functionality to it, +you must create a new function on the file. To run the tests diff --git a/docs/how-to/tour.rst b/docs/how-to/tour.rst new file mode 100644 index 000000000..ddec8ac0d --- /dev/null +++ b/docs/how-to/tour.rst @@ -0,0 +1,123 @@ +Tour of the Code +================ + +Before getting started, it's nice to know a bit about how the project is structured and where +to look for examples. This document aims to provide a brief tour of the +important features of the code. It's aimed at new contributors, but frequent flyers can +skip down to the bottom for a quick reference. + +General Structure +----------------- + +Core Code +********* + +Batavia implements the core language (types, builtins and the core interpreter) are all JavaScript, as well those +portions of the standard library that depend on system services. +This ensures quick execution and less compiling of the compiler. These are organized into the +``builtins``, ``core``, ``modules``, ``stdlib``, and ``types`` files of the main ``/batavia`` directory, with +corresponding subdirectories. Alongside the virtual machine and test suite, this code makes +up the bulk of Batavia. New contributors should start with the ``types`` and ``builtins`` sections +for the best examples to review and copy from. These implementations are the foundation of Python as you know it and +should be immediately familiar to a Python developer. + +Support +******* +You'll also notice folders for tests, docs, and a few other sections, like ``testserver``, which is +a sample deployment with Django that allows you to test code in the browser. Contributions to the +tests and documentation are always welcome and are great ways to familiarize yourself with the +code and meet the other contributors. + +The Core Code +------------- + +A Word on Args & Kwargs +*********************** + +Batavia's implementations of various builtin functions +often **require** ``args`` and ``kwargs`` as input. Here's an example of calling +the repr of a ``my_thing`` object: ``builtins.repr(my_thing, [], {})`` + +The empty [] and {} arguments represent empty argument and keyword argument parameters. +This mimics how Python handles function arguments behind the scenes, and it's important! + +For instance, what happens when you pass a keyword argument into a list? You might say, +"list() doesn't take keyword arguments." In actuality, the list function does receive those +arguments, and the result is that it throws ``TypeError: '' is an invalid keyword +argument for this function`` + +Batavia needs those arguments explicitly specified in a standard format so that it can +check for that case and generate the correct error. The below code examples all use this calling +convention, and you'll be up to your knees in ``BataviaErrors`` if you're not aware of it. + +Building Blocks of Batavia +************************** + +This section is a quick reference for the most common code you'll see. + +builtins.js +^^^^^^^^^^^ + +.. code-block:: javascript + + var builtins = require('./builtins.js') + builtins.abs([-1], {}) // equivalent to abs(-1) + +This contains all of the native Python builtin functions, like ``str``, ``len``, and ``iter``. + +When dealing with Python types, many of the native JavaScript operations have been modified to +try to use builtins first. For instance, ``.toString()`` will often just call the object's ``__str__`` if +possible. Still, the best practice is to use the builtins and types wherever possible. + +types.js +^^^^^^^^ + +.. code-block:: javascript + + var types = require('./types.js') + var my_dict = new types.dict([], {}) + +This contains all of the native Python types that have been implemented in Batavia. It also has some helper functions: + +* ``types.js2py`` Converts a native JavaScript type to a corresponding Batavia type. +* ``types.isinstance`` checks to see if the object is a Python instance of the corresponding type. +* ``types.isbataviainstance`` checks to see if the object is an instance of **any** Batavia type. +* ``types.Type.type_name`` get the name of the type. + +This allows us to avoid ugly things like comparing ``Object.prototype.constructor``. Instead, +use ``types.isinstance`` or ``types.isbataviainstance``. Secondly, it's important that the inputs to Python +types are Pythonized themselves where needed. You should not be making a list() of JavaScript arrays, for +instance. That doesn't make sense! (It may even pass some tests, which is dangerous.) + +core/callables.js +^^^^^^^^^^^^^^^^^ + +These methods ensure that all Python code is executed using the proper ``__call__`` procedure, which could be +overriden or decorated by the programmer. + +* ``callables.call_function`` Invokes a function using its ``__call__`` method if possible. If not, just call it normally. +* ``callables.call_method`` Calls a class method using the call_function specification above. +* ``callables.iter_for_each`` Exhausts an iterable using the call_function specification above. + +As a general rule, use the builtin where possible. If no builtin is available, use the appropriate version +of ``call_function`` instead of calling Python functions and methods directly. An example: + +.. code-block:: javascript + + // Avoid this + my_thing.__len__() + + // Better + var callables = require('./core/callables.js') + callables.call_method(my_thing, '__len__', [], {}) + + // Best + var len = require('./builtins.js').len + len(my_thing, [], {}) + +Note the use of the Batavia calling convention in the two cases above! + +/core/version.js +^^^^^^^^^^^^^^^^ +Some helper functions for distinguishing the version of Python that's running. Outputs +vary from version to version, so it's nice to have this handy. diff --git a/docs/how-to/types.rst b/docs/how-to/types.rst new file mode 100644 index 000000000..94de035f1 --- /dev/null +++ b/docs/how-to/types.rst @@ -0,0 +1,152 @@ +Implementing Python Types in JavaScript +======================================= + +Python's popularity is, in large part, due to the wonderful flexibility of its native types, like ``List`` and ``Dict``. In Batavia, Python native types are the building blocks for all of our other code. +This document will cover the structure of Batavia types and guide you on how to update the existing Batavia implementations. + +Process +------- + +The first thing to do when adding anything to Batavia is to play around a bit with it in the Python REPL. +Here's an example using ``int``:: + + >>> int + + >>> int(1) + 1 + >>> dir(int) + ['__abs__', '__add__', '__and__', '__bool__', '__ceil__', '__class__', '__delattr__', '__dir__', '__divmod__', '__doc__', '__eq__', '__float__', '__floor__', '__floordiv__', '__format__', '__ge__', '__getattribute__', '__getnewargs__', '__gt__', '__hash__', '__index__', '__init__', '__int__', '__invert__', '__le__', '__lshift__', '__lt__', '__mod__', '__mul__', '__ne__', '__neg__', '__new__', '__or__', '__pos__', '__pow__', '__radd__', '__rand__', '__rdivmod__', '__reduce__', '__reduce_ex__', '__repr__', '__rfloordiv__', '__rlshift__', '__rmod__', '__rmul__', '__ror__', '__round__', '__rpow__', '__rrshift__', '__rshift__', '__rsub__', '__rtruediv__', '__rxor__', '__setattr__', '__sizeof__', '__str__', '__sub__', '__subclasshook__', '__truediv__', '__trunc__', '__xor__', 'bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes'] + +Your goal is to find out how the type responds to various inputs and outputs. You may also +want to consult the offical documentation. Once you're a little familiar, you can start to add your +implementation to Batavia. + +Anatomy of a Type +***************** + +Each Python type should be implemented as a JavaScript class. JavaScript handles classes similarly to Python, +but the syntax is very different. Each JavaScript class creates a constructor (similar to Python's ``__init__``), +which is any function that includes the ``this`` keyword, and a prototype, stored in ``.prototype``. +The prototype implements all of the methods for the class, including the constructor, and that's where we'll implement +the type's magic methods and other options. Let's take a look at ``List``. + +.. code-block:: javascript + + // Declare the list class. + function List() { + var builtins = require('../builtins') + + if (arguments.length === 0) { + this.push.apply(this) + } else if (arguments.length === 1) { + // Fast-path for native Array objects. + if (Array.isArray(arguments[0])) { + this.push.apply(this, arguments[0]) + } else { + var iterobj = builtins.iter([arguments[0]], null) + var self = this + callables.iter_for_each(iterobj, function(val) { + self.push(val) + }) + } + } else { + throw new exceptions.TypeError.$pyclass('list() takes at most 1 argument (' + arguments.length + ' given)') + } + } + + function Array_() {} + + Array_.prototype = [] + + List.prototype = Object.create(Array_.prototype) // Duplicates the prototype to avoid damaging the original + List.prototype.length = 0 + create_pyclass(List, 'list', true) // Register the class with Batavia + List.prototype.constructor = List + +This is the constructor, which is called by Batavia when someone invokes ``list()`` or ``[]``. We includes some code to inherit +JavaScript's native array prototype, which has much of the same functionality as List and has lots of quick functions. +You can use JavaScript natives in your implementation; this is a significant speed boost. + +Below that, you'll find all of the member methods added to the prototype. Note that each of these +should return a Python type from ``types.js``. + +.. code-block:: javascript + + List.prototype.__iter__ = function() { + return new ListIterator(this) + } + + List.prototype.__len__ = function() { + var types = require('../types') + return new types.Int(this.length) + } + +List also implements ``.toString()``, a JavaScript function that is sometimes called automatically when a string +conversion is needed. + +.. code-block:: javascript + + List.prototype.toString = function() { + return this.__str__() + } + +Note also the format for errors: ``throw new exceptions..$pyclass``. + +Making Changes +************** + +Make a Test +^^^^^^^^^^^ + +There is much work to be done in the types folder. When making changes, your goal is to match the output +of CPython and the output of the same call made in Batavia. Since we know the desired input and output, +we can use a test and then just fiddle. + +Head over to ``/tests`` and find the ``test_`` file. Many types have a robust test suite, but +do not assume that it is complete. +Follow the format there to add a test for your issue or modify an existing test. +Run it using the following command to check for errors. + +.. code-block:: bash + + $ python setup.py -s tests.path.to.your.test.TestClass.test_function + +Note: ``@expectedFailure`` indicates a test that's not passing yet. Your issue may be tested in one of those cases already. + +Pass the Test +^^^^^^^^^^^^^ + +If the test code runs and fails, you've identified the bug and should have some helpful output comparisons. Head over to +the type you want and start making edits, running your test until it passes. Occasionally, your bug will take you into +other Batavia types and builtins. That's fine too! There are a million places for small omissions all over the codebase. +Just keep in mind that the further you go down the rabbit hole, the more likely you are to have missed something simple. + +Once the test passes, run all tests for the class and see what else broke. (There's always something):: + + $ python setup.py -s tests.path.to.your.test + +After that, it's a good idea to pull the upstream master and check for merge conflicts.:: + + $ git add . + $ git commit -m "" + $ git fetch upstream + $ git rebase origin/master + +If you made major changes, then it may be a good idea to run the full test suite before submitting your pull request.:: + + $ python setup.py -s tests + +(Check out the sidebar for better/faster ways to run the full suite.) Finally, push your code to your fork and submit +your pull request on GitHub to run the CI. Fix any issues and push again until CI passes. The Batavia team will get back +to you with any additional notes and edits. + +Tips +^^^^ + +Your goal is to mimic the CPython implementation as much as possible. If you do so, you'll often fix multiple issues at once. Here's some tips: + +* The original implementation is documented in detail at https://docs.python.org/3/ -- reading up there will definitely improve your understanding. +* If you're inheriting your class from JavaScript, which is very common, you get JavaScript's internal methods for free. Oftentimes, they can be left as is or lightly wrapped. +* Make sure your test properly covers the issue. For instance, if a member function accepts any iterable, make a generic iterable instead of using a list or tuple. +* Make sure method implementations accept args and kwargs, and throw appropriate errors if the input doesn't match. +* Keep your Python REPL open to the side and test your assumptions with lots of inputs and outputs.