Ask Your Question
1

testing sage code

asked 2018-01-30 13:48:10 -0500

ml9nn gravatar image

updated 2018-02-02 17:02:14 -0500

I am having difficulty creating a convenient environment for testing. I typically use pytest when programming in python. One of the nice features of pytest is that on an assertion error

assert a == b

it will tell you what the values of a and b were. I can't seem to get pytest to work with sage. Is it possible to make pytest work with sage? If not, are there other recommended testing/debugging tools to use while writing sage code? (In particular, I want to write unit tests for my code.)

edit retag flag offensive close merge delete

Comments

1

What actual problem are you have? And what do you mean exactly by "work with sage"? If you mean run Sage's own test suite, it won't. Sage has its own test runner. But for testing a package that happens to use Sage as a library it should work, as long as it's pure Python code and not, say, a .sage module (for that to work we'd need a plugin for pytest).

Iguananaut gravatar imageIguananaut ( 2018-02-02 09:45:33 -0500 )edit

@Iguananaut I am talking about writing my own code in the Sage language. I currently write code and save it in .sage files. Perhaps there is another recommended way to write Sage code in a convenient way that I don't know of.

ml9nn gravatar imageml9nn ( 2018-02-02 16:57:55 -0500 )edit

3 answers

Sort by ยป oldest newest most voted
2

answered 2018-02-20 18:20:22 -0500

ml9nn gravatar image

updated 2018-02-20 18:21:56 -0500

When working with sage, the recommended test environment is to use sage's built-in test runner. In this system you write your tests for a function into its docstring. For example, consider myfile.sage:

def my_function(n): 
   r""" Regular old docstring stuff, explaining what the function does.  And then...

    TESTS::

        sage: my_function(1)
        23
        sage: my_function(2)
        91
    """
    do stuff
    return result

To run the tests, use the sage command but with the -t option. So in this example, execute sage -t path/to/myfile.sage. Sage will run the tests, verifying that the code my_function(1) outputs 23. There is a small nuance to be aware of here. It basically tries to simulate the interactive sage shell. So even though my_function(1) outputs the number 23, the string 23 is what is outputted to the interactive sage shell for printing. Whatever string representation would appear in the interactive sage shell is checked against what you type.

If one of your tests fail, this environment will tell you the output that your code generated so that you can compare it to the output you expected!

edit flag offensive delete link more
2

answered 2018-01-31 12:34:22 -0500

dan_fulea gravatar image

updated 2018-02-03 08:14:53 -0500

This will not be a short answer, so let me better first address the shorter issues.

  • A possible IDE to use for debugging purposes is eclipse, install also pydev as plugin inside. This worked for me a long time. Some post that shows how to combine sage devel in eclipse+pydev is here:

    my sage+eclipse+pydev solution (posted as error)

    For short: The IDE must (or should) be started from the terminal, e.g. eclipse &, after setting via

    `sage -sh`
    

    in the "sage shell" all system variables to be used. Inside eclipse, each module should have the usual from sage.all import *, and if sage is configured as python interpreter (instead python2.7), than there may be also some change needed... (The problem in the above post.) Important note: There is no preparsing, so things may become delicate. E.g. the p = 2^8-1 is rejected in eclipse, instead, the code should be written as p = 2**8-1, which is accepted in both worlds, sage and python2. Also, the "intervall" [1..10] does exist only after preparsing, and it is a list of ZZ-numbers, not a list of python integers. A simple replacement of [1..10] by the "naive" range(1,11) may thus fail for "unexpected reasons" in eclipse, although sage may load the code without errors... So this was one of the short issues.

  • A deviation / derivative of eclipse is LiClipse, not free... Never tried it, but one may expect also support after paying for it!

  • An other IDE for python can be used, pycharm. The same discussion as in the case of eclipse applies for it. Here is my parallel answer for the pycharm world:

    38750/how-i-use-sage-with-pycharm-in-ubuntu/

  • the sage (iron python) interpreter is my best friend. Just copy+paste (using %cpaste) short pieces of code, or load bigger chunks. (Or install even bigger ones as packages.) There is an automatic TAB-completion for methods, which makes it productive in a different way, compared with a good editor as emacs, my choice. (I could not make emacsconsume the code. Yet.)

  • And the rest of the post goes to pytest.

The following strategy worked for me, some pytest test sage code could be digested as follows.

First of all, i installed pytest for the python2.7 interpreter. (Default is to use it for python3.) On this manjaro machine i installed the package python2-pytest in version 3,3,2-1 from the community manjaro repository. (This is thus an official manjaro package, and sage is also an official one in this distro! I'll stay with it...)

Then in a terminal: sage -sh - same game as for eclipse and pycharm, this sets all related SAGE paths and libraries. (Environment variables.) The prompter changes correspondingly.

With the new prompter i started:

(sage-sh) dan@k7:asksage$ python2.7 -m pytest pytest_sagetest.py

i.e. the command python2.7 -m pytest pytest_sagetest.py from the folder asksage where the file pytest_sagetest.py with the following content lives in:

# content of pytest_sagetest.py

from sage.all import *

def test_HasseBoundTest():
    """Test the computations of the order of an elliptic curve
    by testing if it stays in the Hasse interval.
    """
    p = 127
    F = GF(p)
    for a in GF(p):
        try:
            E = EllipticCurve( F, [a,0] )
        except Exception:
            continue
        ord = E.order()
        # lower bound check
        assert( ord >= p + 1 - 2*sqrt(p) )
        assert( ord <= p + 1 + 2*sqrt(p) )

def test_LowerHasseBoundTaken():
    p = next_prime(100)
    F = GF(p)
    orders = []
    for a in F:
        for b in F:
            try:
                E = EllipticCurve( F, [a,b] )
            except Exception:
                pass    # discriminant is zero...
        ord = E.order()
        if ord not in orders:
            orders.append( ord )
        # lower bound check
    assert( min(orders) ==  ceil(p + 1 - 2*sqrt(p)) )
    assert( max(orders) == floor(p + 1 + 2*sqrt(p)) )


def test_AllOrdersTaken():
    p = 71
    F = GF(p)
    orders = []
    for a in F:
        for b in F:
            try:
                E = EllipticCurve( F, [a,b] )
            except Exception:
                pass    # discriminant is zero...
        ord = E.order()
        if ord not in orders:
            orders.append( ord )
        # lower bound check
    orders.sort()
    assert( orders == range( ceil (p + 1 - 2*sqrt(p)) ,
                             floor(p + 1 + 2*sqrt(p)) + 1 ) )

It was an intention to produce "errors". (Of course, some not so intentioned errors are also present... Since we want more errors, they remained in the landscape.) Then the pytest digestive delivers the following information:

(sage-sh) dan@k7:asksage$ python2.7 -m pytest pytest_sagetest.py
================================ test session starts ================================
platform linux2 -- Python 2.7.14, pytest-3.3.2, py-1.5.0, pluggy-0.6.0
rootdir: /home/dan/asksage/asksage, inifile:
collected 3 items                                                                   

pytest_sagetest.py .FF                                                        [100%]

===================================== FAILURES ======================================
_____________________________ test_LowerHasseBoundTaken _____________________________

    def test_LowerHasseBoundTaken():
        p = next_prime(100)
        F = GF(p)
        orders = []
        for a in F:
            for b in F:
                try:
                    E = EllipticCurve( F, [a,b] )
                except Exception:
                    pass    # discriminant is zero...
            ord = E.order()
            if ord not in orders:
                orders.append( ord )
            # lower bound check
>       assert( min(orders) ==  ceil(p + 1 - 2*sqrt(p)) )
E       assert 83 == 82
E        +  where 83 = min([102, 105, 92, 114, 112, 87, ...])
E        +  and   82 = ceil(((101 + 1) - (2 * sqrt(101))))
E        +    where sqrt(101) = sqrt(101)

pytest_sagetest.py:37: AssertionError
________________________________ test_AllOrdersTaken ________________________________

    def test_AllOrdersTaken():
        p = 71
        F = GF(p)
        orders = []                                                                  
        for a in F:                                                                  
            for b in F:                                                              
                try:                                                                 
                    E = EllipticCurve( F, [a,b] )                                    
                except Exception:                                                    
                    pass    # discriminant is zero...                                
            ord = E.order()                                                          
            if ord not in orders:                                                    
                orders.append( ord )                                                 
            # lower bound check                                                      
        orders.sort()
>       assert( orders == range( ceil (p + 1 - 2*sqrt(p)) ,
                                 floor(p + 1 + 2*sqrt(p)) + 1 ) )
E       assert [56, 57, 59, 60, 61, 62, ...] == [56, 57, 58, 59, 60, 61, ...]
E         At index 2 diff: 59 != 58
E         Right contains more items, first extra item: 85
E         Use -v to get the full diff

pytest_sagetest.py:56: AssertionError
================================= warnings summary ==================================
pytest_sagetest.py::TestSuite
  cannot collect test class 'TestSuite' because it has a __init__ constructor

-- Docs: http://doc.pytest.org/en/latest/warnings.html
================== 2 failed, 1 passed, 1 warnings in 7.43 seconds ===================
(sage-sh) dan@k7:asksage$

(And here there is a lot of colourful highlight gone. Just start in a proper termina...)

Note: pytest is rather a "unit test" tool, it may be used in a "test driven devel" process. Personally, for my research this is too much (i.e. life is too short). But this is of huge importance while for devel+maintain of the packages. Sage has also a proper mechanism for doing this. In fact, after installing sage one has already the chance to test it against the unit test code sage comes with...

LATER EDIT :: import issues

Note that pytest has a specific way to use a python path.

https://docs.pytest.org/en/latest/pythonpath.html

There is even a package pytest-pythonpath that may make complicated things easier.

https://pypi.python.org/pypi/pytest-pythonpath

In our case, a simplistic situation as the following one could import a module (in the same directory).

Here is the situation:

[dan@k7 ~/asksage/asksage]$ ls tests
mycomputations.py  test_sage.py
[dan@k7 ~/asksage/asksage]$ cat tests/mycomputations.py 
# content of mycomputations.py
from sage.all import *

def PythonInteger(k):
    return int(k)

def ZZInteger(k):
    return ZZ(k)
[dan@k7 ~/asksage/asksage]$ cat tests/test_sage.py 
# content of test_sage.py
from sage.all import *
import mycomputations

def test_type1():
    a = mycomputations.PythonInteger(100)
    assert type(a) == type(ZZ(0))

def test_type2():
    a = mycomputations.ZZInteger(100)
    assert type(a) == type(ZZ(0))

[dan@k7 ~/asksage/asksage]$

Then:

[dan@k7 ~/asksage/asksage]$ pytest2 tests
======================== test session starts =========================
platform linux2 -- Python 2.7.14, pytest-3.3.2, py-1.5.0, pluggy-0.6.0
rootdir: /home/dan/asksage/asksage, inifile:
collected 2 items                                                    

tests/test_sage.py F.                                          [100%]

============================== FAILURES ==============================
_____________________________ test_type1 _____________________________

    def test_type1():
        a = mycomputations.PythonInteger(100)
>       assert type(a) == type(ZZ(0))
E       AssertionError: assert <type 'int'> == <type 'sage.rings.integer.Integer'>
E        +  where <type 'int'> = type(100)
E        +  and   <type 'sage.rings.integer.Integer'> = type(0)
E        +    where 0 = ZZ(0)

tests/test_sage.py:7: AssertionError
========================== warnings summary ==========================
tests/test_sage.py::TestSuite
  cannot collect test class 'TestSuite' because it has a __init__ constructor

-- Docs: http://doc.pytest.org/en/latest/warnings.html
=========== 1 failed, 1 passed, 1 warnings in 3.69 seconds ===========
[dan@k7 ~/asksage/asksage]$

More complicated unit test situations are covered by the pytest documentation / framework. If such issues remain, please consider using an IDE, eclipse and/or pycharm, both are comming with a supported unit test environment.

The post also mentions (asks for) debugging possibilities, same answer, eclipse+pydev and/or pycharm so far.

Personal note: People that have not used such IDEs (e.g. because having a pure mathematical formation and/or because using rather the notebook part of sage) will discover a new world with good productivity mechanisms.

edit flag offensive delete link more

Comments

This seems like a way off-topic answer. All they want to know is how to use pytest with code that uses Sage (I'm guessing--it wasn't clear). All the stuff about IDE's seems unrelated...?

Iguananaut gravatar imageIguananaut ( 2018-02-02 09:43:59 -0500 )edit
1

Yes, Iguananaut is right. Perhaps the IDE's were recommended because they have some debugging capabilities. But really I just want pytest or similar "unit testing" capabilities. The part of this answer about pytest seems relevant, with the exception that it doesn't import any code written by the programmer. Could I add import myfile.sage or load('myfile.sage') to the top of this pytest_sagetest.py file? I have tried that already to no avail. How can I import my code?

ml9nn gravatar imageml9nn ( 2018-02-02 21:20:40 -0500 )edit

There was a part of the post, the question: If not, are there other recommended testing/debugging tools to use while writing sage code? (In particular, I want to write unit tests for my code.) One possible answer is eclipse, since it supports unit testing. (And debugging.) Moreover, eclipse easily uses code from the same project (=eclipse folder). Project references can be also easily configured to use code from one project in an other one.

The import side of the question is new. (A block of ten lines of minimal code, that should be parsed by pytest for the own purposes, helps a lot in such case. I saw only an assertion with two values in the post, no explicit description to start pytest. This makes any answer programatically unsatisfactory, if such points matter.)

I reedited.

dan_fulea gravatar imagedan_fulea ( 2018-02-03 08:15:14 -0500 )edit

Thank you for your responses, Dan. I appreciate your time and effort. I will get back to this thread in a few days.

ml9nn gravatar imageml9nn ( 2018-02-03 16:52:02 -0500 )edit
0

answered 2018-01-31 06:20:36 -0500

eric_g gravatar image

Just a partial answer, regarding the debugging tools: you can use the command trace for interactively tracing some Sage code. Unfortunately its works only for Python parts of Sage, not for Cython ones.

edit flag offensive delete link more

Your Answer

Please start posting anonymously - your entry will be published after you log in or create a new account.

Add Answer

Question Tools

Stats

Asked: 2018-01-30 13:48:10 -0500

Seen: 148 times

Last updated: Feb 20