# -*- tcl -*-
# Tests for the comm module.
#
# Sourcing this file into Tcl runs the tests and generates output for errors.
# No output means no errors were found.
#
# Copyright (c) 2001 by ActiveState Tool Corp.
# All rights reserved.
#
# RCS: @(#) $Id: comm.test,v 1.14 2010/09/15 19:48:33 andreas_kupries Exp $

# -------------------------------------------------------------------------

source [file join \
	[file dirname [file dirname [file join [pwd] [info script]]]] \
	devtools testutilities.tcl]

testsNeedTcl     8.3 ; # snit
testsNeedTcltest 1.0

tcltest::testConstraint hastls [expr {![catch {package require tls}]}]

support {
    # Using snit1 here, whatever the version of Tcl
    use snit/snit.tcl snit
}
testing {
    useLocal comm.tcl comm
}

# ------------------------------------------------------------------------
# First order of things is to spawn a separate tclsh into the background
# and have it execute comm too, with some general code to respond to our
# requests

useLocalFile comm.slaveboot

# ------------------------------------------------------------------------

test comm-1.0 {set remote variable} {
    ::comm::comm send [slave] {set foo b}
} {b}

test comm-1.1 {set remote variable, async} {
    ::comm::comm send -async [slave] {set fox a}
} {}

test comm-1.2 {get remote variables} {
    ::comm::comm send [slave] {list $foo $fox}
} {b a}

# ------------------------------------------------------------------------

set hack [interp create]

test comm-2.0 {-interp configuration} {
    ::comm::comm configure -interp $hack
} {}

test comm-2.1 {-interp configuration} {
    ::comm::comm configure -interp 
} $hack

test comm-2.2 {-interp configuration} {
    res!
    res+ [::comm::comm configure -interp $hack] [::comm::comm configure -interp]
    res+ [::comm::comm configure -interp {}]    [::comm::comm configure -interp]
    res?
} [list [list {} $hack] {{} {}}]

test comm-2.3 {-interp configuration} {
    catch {::comm::comm configure -interp bad} msg
    set msg
} {Non-interpreter to configuration option: -interp}

test comm-2.4 {-interp configuration, destruction} {
    res!
    res+ [interp exists $hack]
    res+ [info commands FOO]
    comm::comm new FOO -interp $hack
    FOO destroy
    res+ [interp exists $hack]
    res+ [info commands FOO]
    res?
} {1 {{}} 0 {{}}}

set hack [interp create]
set beta [interp create]

test comm-2.5 {-interp configuration, destruction} {
    res!
    res+ [interp exists $hack]
    res+ [interp exists $beta]
    res+ [info commands FOO]
    comm::comm new FOO -interp $hack
    FOO configure      -interp $beta
    FOO destroy
    res+ [interp exists $hack]
    res+ [interp exists $beta]
    res+ [info commands FOO]
    res?
} {1 1 {{}} 1 0 {{}}}

test comm-2.6 {-interp use for received scripts} {
    set FOO [::comm::comm send [slave] {
	set hack [interp create]
	interp eval $hack {set fox 0}
	comm::comm new FOO -interp $hack -listen 1
	FOO self
    }] ; # {}

    comm::comm send $FOO {set fox 1}
    set res [comm::comm send [slave] {
	interp eval $hack {set fox}
    }] ; # {}
    comm::comm send [slave] {FOO destroy}
    set res
} 1

test comm-2.7 {-interp use for received scripts} {
    set FOO [::comm::comm send [slave] {
	set hack [interp create]
	interp eval $hack {set fox 0}
	comm::comm new FOO -interp $hack -listen 1
	FOO self
    }] ; # {}

    comm::comm send $FOO set fox 2
    set res [comm::comm send [slave] {
	interp eval $hack {set fox}
    }] ; # {}
    comm::comm send [slave] {FOO destroy}
    set res
} 2

# ------------------------------------------------------------------------

test comm-3.0 {-events configuration} {
    ::comm::comm configure -events eval
} {}

test comm-3.1 {-events configuration} {
    ::comm::comm configure -events
} eval

test comm-3.2 {-events configuration} {
    res!
    res+ [::comm::comm configure -events eval] [::comm::comm configure -events]
    res+ [::comm::comm configure -events {}]   [::comm::comm configure -events]
    res?
} {{{} eval} {{} {}}}

test comm-3.3 {-events configuration} {
    catch {::comm::comm configure -events bad} msg
    set msg
} {Non-event to configuration option: -events}


test comm-3.4 {-interp use for -events scripts, eval} {
    set FOO [::comm::comm send [slave] {
	set hack [interp create]
	interp eval $hack {set fox 0 ; set wolf 0}
	comm::comm new FOO -interp $hack -listen 1 -events eval
	FOO hook eval {set wolf 2}
	FOO self
    }] ; # {}

    comm::comm send $FOO {set fox 1}
    set res [comm::comm send [slave] {
	interp eval $hack {set wolf}
    }] ; # {}
    comm::comm send [slave] {FOO destroy}
    set res
} 2

# ------------------------------------------------------------------------

test comm-4.0 {async generation of result on remote side} {
    ::comm::comm send [slave] {
	proc future {} {
	    set f [comm::comm return_async]
	    after 3000 [list $f return "delayed $f"]
	    return ignored
	}
    }
    ::comm::comm send [slave] {future}
} {delayed ::comm::future1}

test comm-4.1 {async reception of a result via callback} {
    set res {}
    proc foo {args} {
	array set tmp $args
	unset tmp(-id)
	unset tmp(-serial)
	global res ; lappend res [dictsort [array get tmp]]
    }
    ::comm::comm send -command foo [slave] {list $foo $fox}
    vwait res
    rename foo {}
    set res
} {{-chan ::comm::comm -code 0 -errorcode {} -errorinfo {} -result {b a}}}

test comm-4.2 {async generation/reception of results in parallel} {

    # Setup long running operations with async result generation.
    ::comm::comm send [slave] {
	proc future {n x} {
	    set f [comm::comm return_async]
	    after $n [list $f return "delayed $x"]
	    return ignored
	}
    }

    # Setup async receiver callback.
    proc receive {args} {
	global res tick tock
	array set tmp $args
	unset tmp(-id)
	unset tmp(-serial)
	unset tmp(-chan)
	unset tmp(-code)
	unset tmp(-errorcode)
	unset tmp(-errorinfo)
	lappend res [dictsort [array get tmp]]
	incr tock -1
	if {!$tock} {set tick .}
	return
    }

    # Execute two requests, the second of which is returns before the first.
    # The main point is that the server does process it due to first doing
    # an async return.

    set tick .
    set tock 2
    set res {}

    ::comm::comm send -command receive [slave] {future 5000 A}
    ::comm::comm send -command receive [slave] {future 2500 B}
    vwait tick
    rename receive {}
    set res
    # B returned before A, A was sent before B
} {{-result {delayed B}} {-result {delayed A}}}


test comm-4.3 {bug 2972571, handling of \\ by Word0} {
    ::comm::comm send [slave] {
	proc foo {args} {
	    return nothing
	}
    }
    ::comm::comm send [slave] {foo \\}
} {nothing}


# ------------------------------------------------------------------------

test comm-5.0 {-port already in use} {
    # First start a server on port 12345
    set port 12345
    catch {set shdl [socket -server foo $port]}
    catch {::comm::comm new bar -port $port -listen 1 -local 0} msg
    catch {close $shdl}
    unset -nocomplain shdl port
    set msg
} {couldn't open socket: address already in use}

# ------------------------------------------------------------------------

test comm-6.0 {secured communication via tls package} hastls {
    # Setup secured channel in main process.
    tls::init \
	-keyfile  [tcllibPath devtools/receiver.key] \
	-certfile [tcllibPath devtools/receiver.crt] \
	-cafile   [tcllibPath devtools/ca.crt] \
	-ssl2 1    \
	-ssl3 1    \
	-tls1 0    \
	-require 1
    comm::comm new BAR -socketcmd tls::socket -listen 1

    # Setup secured channel in slave process
    ::comm::comm send [slave] {
	package require tls
	set fox dog
    }
    ::comm::comm send [slave] \
	[list \
	     tls::init \
	     -keyfile  [tcllibPath devtools/transmitter.key] \
	     -certfile [tcllibPath devtools/transmitter.crt] \
	     -cafile   [tcllibPath devtools/ca.crt] \
	     -ssl2 1    \
	     -ssl3 1    \
	     -tls1 0    \
	     -require 1]
    set FOO [::comm::comm send [slave] {
	comm::comm new FOO -socketcmd tls::socket -listen 1
	FOO self
    }] ; # {}

    # Run command interaction over the secured channel
    set res [BAR send $FOO {set fox}]

    # Cleanup, remove secured endpoints
    comm::comm send [slave] {FOO destroy}
    BAR destroy

    # Return result of the secured command
    set res
} dog

# ------------------------------------------------------------------------

slavestop
testsuiteCleanup
return
