if {[lsearch [namespace children] ::tcltest] == -1} {
	package require tcltest
	namespace import ::tcltest::*
}

package require tclpy

# =========
# PY
# =========
test py-1.1 {incorrect usage} {
	list [catch {py} errMsg] $errMsg
} {1 {wrong # args: should be "py subcommand ?arg ...?"}}

# =========
# PY EVAL
# =========
test py_eval-1.1 {incorrect eval usage} {
	list [catch {py eval} errMsg1] $errMsg1\
	     [catch {py eval {print 1} {print 2}} errMsg2] $errMsg2
} {1 {wrong # args: should be "py eval evalString"}\
   1 {wrong # args: should be "py eval evalString"}}

test py_eval-1.2 {eval returns nothing} {
	list [py eval {1+1}]
} {{}}

test py_eval-1.2 {basic eval} -setup {
	set randdir tmp_[expr {rand()}]
} -body {
	py eval "def mk(dir): os.mkdir(dir)"
	py eval "import os; mk('$randdir')"
	file isdirectory $randdir
} -result {1} -cleanup {
	file delete $randdir
}

# =========
# PY IMPORT
# =========
test py_import-1.1 {incorrect import usage} {
	list [catch {py import} errMsg1] $errMsg1\
	     [catch {py import -from os} errMsg2] $errMsg2
} {1 {wrong # args: should be "py import module"}\
   1 {wrong # args: should be "py import module"}}

test py_import-1.2 {basic import} -body {
	py import re
	py eval "import sys; assert 're' in sys.modules; assert 're' in globals()"
} -result {}

test py_import-1.3 {submodule import} -body {
	py import xml.dom
	py eval "assert 'dom' in dir(xml)"
} -result {}

test py_import-1.4 {non-existent import} -body {
	list [catch {py import aosidas} err] $err
} -result {1 {ModuleNotFoundError: No module named 'aosidas'
----- tcl -> python interface -----}}

test py_import-1.5 {non-existent import with full trace} -body {
	proc aaa {} {py import aosidas}
	list [catch {aaa} err] $err $::errorInfo
} -result {1 {ModuleNotFoundError: No module named 'aosidas'
----- tcl -> python interface -----} {ModuleNotFoundError: No module named 'aosidas'
----- tcl -> python interface -----
    while executing
"py import aosidas"
    (procedure "aaa" line 1)
    invoked from within
"aaa"}}

# =========
# PY CALL
# =========
test py_call-1.1 {incorrect call usage} {
	list [catch {py call} errMsg1] $errMsg1
} {1 {wrong # args: should be "py call func ?arg ...?"}}

test py_call-1.2 {basic call} {
	py eval {def a(): return 5**2}
	py call a
} {25}

test py_call-1.3 {basic call with args} {
	py eval {def a(x,y): return x+y}
	py call a string1 string2
} {string1string2}

test py_call-1.4 {call of module function} {
	py import base64
	py call base64.b64decode YXRlc3Q=
} {atest}

test py_call-1.5 {call of object methods} {
	py import io
	py eval {a = io.StringIO(); a.write('btest'); a.seek(0)}
	py call a.read
} {btest}

test py_call-1.6 {simple call exception} {
	py eval {a = lambda: 1/0}
	list [catch {py call a} err] $err
} {1 {ZeroDivisionError: division by zero
  File "<string>", line 1, in <lambda>
----- tcl -> python interface -----}}

test py_call-1.7 {stacked call exception} {
	py eval {def a(): return 5 + dict()}
	py eval {def b(): return a()}
	py eval {def c(): return b()}
	proc d {} {py call c}
	proc e {} {d}
	list [catch {e} err] $err $::errorInfo
} {1 {TypeError: unsupported operand type(s) for +: 'int' and 'dict'
  File "<string>", line 1, in a
  File "<string>", line 1, in b
  File "<string>", line 1, in c
----- tcl -> python interface -----} {TypeError: unsupported operand type(s) for +: 'int' and 'dict'
  File "<string>", line 1, in a
  File "<string>", line 1, in b
  File "<string>", line 1, in c
----- tcl -> python interface -----
    while executing
"py call c"
    (procedure "d" line 1)
    invoked from within
"d"
    (procedure "e" line 1)
    invoked from within
"e"}}

# TODO: this error message is terrible
test py_call-1.8 {call of nonexistent functions} {
	list [catch {py call aosdin} err] $err
} {1 {AttributeError: module '__main__' has no attribute 'aosdin'
----- tcl -> python interface -----}}

# TODO: this error message could be improved ("has no method")
test py_call-1.9 {call of nonexistent object methods} {
	py eval {a = "aaa"}
	list [catch {py call a.aosdin} err] $err
} {1 {AttributeError: 'str' object has no attribute 'aosdin'
----- tcl -> python interface -----}}

# =========
# TYPES
# =========
test types-1.1 {return True} {
	py eval {def a(): return True}
	py call a
} {1}

test types-1.2 {return False} {
	py eval {def a(): return False}
	py call a
} {0}

test types-1.3 {return None} {
	py eval {def a(): return None}
	py call a
} {}

test types-1.4 {return null byte} {
	py eval {def a(): return '\0'}
	set a [py call a]
	list [string length $a] [expr {$a == "\0"}]
} {1 1}

# See gotcha 1 for explanation of roundabout way of getting a unicode object.
test types-1.5 {return unicode object} {
	py eval {def a(): return b'\xe0\xb2\xa0'.decode('utf-8')}
	expr {[py call a] == "ಠ"}
} {1}

test types-1.6 {return literal bytes} {
	py eval {def a(): return '\xe0\xb2\xa0'}
	expr {[py call a] == "\xe0\xb2\xa0"}
} {1}

test types-1.7 {return nested lists and dictionaries} {
	py eval {def a(): return [
			(1,2),
			[u"a",["b",7]],
			{"x":[(3,4),{'a':{'b':'c'}}],"y":(4,5,6)}]}
	set a [py call a]
	set ad [lindex $a 2]
	set ada [lindex [dict get $ad x] 1]
	list \
		[lindex $a 0] [lindex $a 1] [lindex [dict get $ad x] 0]\
		[dict get $ad y] [dict get [dict get $ada a] b]
} {{1 2} {a {b 7}} {3 4} {4 5 6} c}

test types-1.8 {return float} {
	py eval {def a(): return 1.0/3}
	py call a
} {0.3333333333333333}

test types-1.9 {return large integer} {
	py eval {def a(): return 3 << 5000}
	expr {[py call a] == 3 << 5000}
} {1}

# =========
# MODULES
# =========
test modules-1.1 {hashlib module} {
	py import hashlib
	py eval {def a(): return hashlib.sha1('password'.encode('utf8')).hexdigest()}
	py call a
} {5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8}

test modules-1.2 {zlib module} {
	py import zlib
	py eval {def a(): return zlib.decompress(b'x\x9cKLL\x04\x00\x02I\x01$')}
	py call a
} {aaa}

test modules-1.3 {datetime module} {
	py import datetime
	py eval {def a(): return datetime.datetime.utcfromtimestamp(0).isoformat()}
	py call a
} {1970-01-01T00:00:00}

test module-1.4 {sqlite3 module} {
	py import sqlite3
	py eval {def a(): global b; b = sqlite3.connect(":memory:").cursor()}
	py call a
	py call b.execute "create table x(y integer)"
	py call b.execute "insert into x values (1)"
	py call b.execute "insert into x values (18)"
	py call b.execute "select avg(y) from x"
	py call b.fetchone
} {9.5}

# =========
# cleanup
# =========
::tcltest::cleanupTests
