1. 16 Jan, 2019 1 commit
  2. 30 Oct, 2018 4 commits
    • Kirill Smelkov's avatar
      Add support for defer & recover · 5146eb0b
      Kirill Smelkov authored
      `defer` allows to schedule a cleanup to be executed when current function
      returns. It is similar to `try`/`finally` but does not force the cleanup part
      to be far away in the end. For example::
      
         wc = wcfs.join(zurl)    │     wc = wcfs.join(zurl)
         defer(wc.close)         │     try:
                                 │        ...
         ...                     │        ...
         ...                     │        ...
         ...                     │     finally:
                                 │        wc.close()
      
      For completeness there is `recover` and `panic` that allow to program with
      Go-style error handling, for example::
      
         def _():
            r = recover()
            if r is not None:
               print("recovered. error was: %s" % (r,))
         defer(_)
      
         ...
      
         panic("aaa")
      
      But `recover` and `panic` are probably of less utility since they can be
      practically natively modelled with `try`/`except`.
      
      If `defer` is used, the function that uses it must be wrapped with `@func` or
      `@method` decorators.
      
      The implementation is partly inspired by work of Denis Kolodin:
      
      - https://habr.com/post/191786
      - https://stackoverflow.com/a/43028386/9456786
      5146eb0b
    • Kirill Smelkov's avatar
      select: Another select-select test which reuses channels during iterations · 2fc6797c
      Kirill Smelkov authored
      Previous select-select test uses 2 channels and recreats them every
      iteration. However the bug we just saw in b51b8d5d (select: Run tests
      more thoroughly) is of parasitic nature - where misbehaviour depends on
      what state has been left there from previous select and whether the race
      to get to that state fast enough succeeded.
      
      So add a more secialized test which tries to trigger the parasitic
      effects that depend on previous state left by select on the channels.
      
      The test, similarly to b51b8d5d, currently fails.
      2fc6797c
    • Kirill Smelkov's avatar
      select: Run tests more thoroughly · b51b8d5d
      Kirill Smelkov authored
      With more iterations it triggers a bug:
      
      	kirr@deco:~/src/tools/go/pygolang$ py.test -vsx
      	============================================== test session starts ===============================================
      	platform linux2 -- Python 2.7.15+, pytest-3.6.4, py-1.6.0, pluggy-0.6.0 -- /usr/bin/python2
      	cachedir: .pytest_cache
      	rootdir: /home/kirr/src/tools/go/pygolang, inifile:
      	collected 7 items
      
      	golang/_gopath_test.py::test_import_module PASSED
      	golang/_gopath_test.py::test_import_package PASSED
      	golang/gcompat_test.py::test_qq PASSED
      	golang/golang_test.py::test_go PASSED
      	golang/golang_test.py::test_chan PASSED
      	golang/golang_test.py::test_select Exception in thread Thread-117:
      	Traceback (most recent call last):
      	  File "/usr/lib/python2.7/threading.py", line 801, in __bootstrap_inner
      	    self.run()
      	  File "/usr/lib/python2.7/threading.py", line 754, in run
      	    self.__target(*self.__args, **self.__kwargs)
      	  File "/home/kirr/src/tools/go/pygolang/golang/golang_test.py", line 320, in _
      	    assert (_, _rx) == (1, 'b')
      	AssertionError: assert (1, 'xxx1') == (1, 'b')
      	  At index 1 diff: 'xxx1' != 'b'
      	  Full diff:
      	  - (1, 'xxx1')
      	  + (1, 'b')
      b51b8d5d
    • Kirill Smelkov's avatar
      go: Fix test_go when running in-tree without pygolang being installed · ec929991
      Kirill Smelkov authored
      This test was failing when pygolang was not dev-mode installed (`pip install -e .`):
      
      	kirr@deco:~/src/tools/go/pygolang$ py.test
      	=========================================== test session starts ============================================
      	platform linux2 -- Python 2.7.15+, pytest-3.6.4, py-1.6.0, pluggy-0.6.0
      	rootdir: /home/kirr/src/tools/go/pygolang, inifile:
      	collected 8 items
      
      	golang/_gopath_test.py ..                                                                            [ 25%]
      	golang/gcompat_test.py .                                                                             [ 37%]
      	golang/golang_test.py F....                                                                          [100%]
      
      	================================================= FAILURES =================================================
      	_________________________________________________ test_go __________________________________________________
      
      	    def test_go():
      	        # leaked goroutine behaviour check: done in separate process because we need
      	        # to test process termination exit there.
      	        subprocess.check_call([sys.executable,
      	>           dirname(__file__) + "/golang_test_goleaked.py"])
      
      	golang/golang_test.py:38:
      	_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
      
      	popenargs = (['/usr/bin/python2', '/home/kirr/src/tools/go/pygolang/golang/golang_test_goleaked.py'],)
      	kwargs = {}, retcode = 1
      	cmd = ['/usr/bin/python2', '/home/kirr/src/tools/go/pygolang/golang/golang_test_goleaked.py']
      
      	    def check_call(*popenargs, **kwargs):
      	        """Run command with arguments.  Wait for command to complete.  If
      	        the exit code was zero then return, otherwise raise
      	        CalledProcessError.  The CalledProcessError object will have the
      	        return code in the returncode attribute.
      
      	        The arguments are the same as for the Popen constructor.  Example:
      
      	        check_call(["ls", "-l"])
      	        """
      	        retcode = call(*popenargs, **kwargs)
      	        if retcode:
      	            cmd = kwargs.get("args")
      	            if cmd is None:
      	                cmd = popenargs[0]
      	>           raise CalledProcessError(retcode, cmd)
      	E           CalledProcessError: Command '['/usr/bin/python2', '/home/kirr/src/tools/go/pygolang/golang/golang_test_goleaked.py']' returned non-zero exit status 1
      
      	/usr/lib/python2.7/subprocess.py:190: CalledProcessError
      	------------------------------------------- Captured stderr call -------------------------------------------
      	Traceback (most recent call last):
      	  File "/home/kirr/src/tools/go/pygolang/golang/golang_test_goleaked.py", line 23, in <module>
      	    from golang import go, chan
      	ImportError: No module named golang
      	==================================== 1 failed, 7 passed in 0.10 seconds ====================================
      
      Fix it by injecting top-level pygolang/ into $PYTHONPATH when testing
      via external processes.
      
      Fixes 69cef96e (go: Don't allow leaked goroutines to prevent program to exit)
      ec929991
  3. 25 Oct, 2018 1 commit
    • Kirill Smelkov's avatar
      @method: Fix it for Python3 · ab69e0fa
      Kirill Smelkov authored
      There was no test for @method so far and that's why it went unnoticed.
      But on Python3 it breaks on f.func_name:
      
      	In [3]: def f(): pass
      
      	In [4]: f.func_name
      	---------------------------------------------------------------------------
      	AttributeError                            Traceback (most recent call last)
      	<ipython-input-4-662dcbac1531> in <module>()
      	----> 1 f.func_name
      
      	AttributeError: 'function' object has no attribute 'func_name'
      
      Fix it by using f.__name__ which works on both py2 and py3.
      
      Add test for @method to make sure it doesn't break unnoticed.
      ab69e0fa
  4. 24 Oct, 2018 1 commit
    • Kirill Smelkov's avatar
      go: Don't allow leaked goroutines to prevent program to exit · 69cef96e
      Kirill Smelkov authored
      This is the Go behaviour, as demonstratd by the following program:
      
      ---- 8< ----
      package main
      
      import (
      	"fmt"
      	"time"
      )
      
      func work(w int) {
      	for i := 0; ; i++ {
      		fmt.Printf("w%d: %d\n", w, i)
      		time.Sleep(1*time.Second)
      	}
      }
      
      func main() {
      	for i := 0; i < 100; i++ {
      		go work(i)
      	}
      
      	time.Sleep(3*time.Second)
      	println("main: exit")
      }
      ---- 8< ----
      69cef96e
  5. 20 Jun, 2018 1 commit
    • Kirill Smelkov's avatar
      Turn pygopath into full pygolang · afa46cf5
      Kirill Smelkov authored
      Not only we can import modules by full path, but now we can also spawn
      threads/coroutines and exchange data in between them with the same
      primitives and semantic as in Go.
      
      The bulk of new functionality is copied from here:
      
      	kirr/go123@9e1aa6ab
      
      Original commit description follows:
      
      """
      golang: New _Python_ package to provide Go-like features to Python language
      - `go` spawns lightweight thread.
      - `chan` and `select` provide channels with Go semantic.
      - `method` allows to define methods separate from class.
      - `gimport` allows to import python modules by full path in a Go workspace.
      
      The focus of first draft was on usage interface and on correctness, not speed.
      In particular select should be fully working.
      
      If there is a chance I will maybe try to followup with gevent-based
      implementation in the future.
      Hide whitespace changes
      """
      afa46cf5