# Copyright (c) 2003-2020, Ashok P. Nadkarni
# All rights reserved.
#
# See the file LICENSE for license

# This file contains tests for commands from the process.tcl


package require tcltest
eval tcltest::configure $argv

source [file join [file dirname [info script]] testutil.tcl]
load_twapi_package twapi_process
# For now we need ui to find windows. Try getting rid of it. TBD
package require twapi_ui

#
# General definitions and procs used in the tests
namespace eval ::twapi::process::test {
    namespace import ::tcltest::test
    variable field
    variable testnum
    variable type

    ::tcltest::testConstraint win2k [twapi::min_os_version 5]
    # Constraint wow64 is true if we are a 32-bit process on a 64-bit system
    ::tcltest::testConstraint wow64 [expr {$::tcl_platform(machine) eq "intel" && [info exists ::env(ProgramW6432)]}]
    # Constraint x64 is true if we are a 64-bit process
    ::tcltest::testConstraint x64 [expr {$::tcl_platform(machine) eq "amd64"}]

    # Whether OS supports integrity
    variable target_integrity
    variable have_integrity [twapi::min_os_version 6]
    if {$have_integrity} {
        set target_integrity 4096
    } else {
        set target_integrity 8192
    }

    # Verify pid $pid has name $name
    proc verify_pid_name {pid name {refresh 0}} {
        return [string equal -nocase [get_process_field $pid -name $refresh] $name]
    }

    # Verify all get_process_info fields are present
    proc verify_get_process_info_fieldnames {kl} {
        verify_kl_fields $kl {
            -basepriority
            -elevation
            -parent
            -tssession
            -name
            -createtime
            -usertime
            -privilegedtime
            -elapsedtime
            -handlecount
            -pagefaults
            -pagefilebytes
            -pagefilebytespeak
            -poolnonpagedbytes
            -poolnonpagedbytespeak
            -poolpagedbytes
            -poolpagedbytespeak
            -threadcount
            -virtualbytes
            -virtualbytespeak
            -workingset
            -workingsetpeak
            -tids
            -ioreadops
            -iowriteops
            -iootherops
            -ioreadbytes
            -iowritebytes
            -iootherbytes
            -user
            -groups
            -groupsids
            -groupattrs
            -restrictedgroups
            -restrictedgroupattrs
            -primarygroup
            -primarygroupsid
            -privileges
            -enabledprivileges
            -disabledprivileges
            -priorityclass
            -logonsession
            -pid
            -path
            -commandline
            -virtualized
            -integrity
            -integritylabel
        }
    }

    # Verify as many process fields as possible
    proc verify_processinfo {pid v_procinfo refresh {verify_all 0}} {
        variable psinfo
        upvar $v_procinfo procinfo

        get_processes $refresh

        set mismatches [list ]
        foreach field {-basepriority -pid -parent} {
            if {[get_process_field $pid $field] != $procinfo($field)} {
                lappend mismatches "Mismatch in field $field"
            }
        }
        if {! [string is integer -strict $procinfo(-priorityclass)]} {
            lappend mismatches "Field -priorityclass is not an integer."
        }

        if {[string compare -nocase [get_process_field $pid -name] $procinfo(-name)]} {
            lappend mismatches "Mismatch in field -name"
        }
        
        if {![equal_paths [get_process_field $pid -path] $procinfo(-path)]} {
                lappend mismatches "Mismatch in field -path ([get_process_field $pid -path] != $procinfo(-path))"
            }

        # Logon session - just check syntax
        if {! [regexp {^[[:xdigit:]]{8}-[[:xdigit:]]{8}$} $procinfo(-logonsession)]} {
            lappend mismatches "Wrong syntax in field -logonsession"
        }

        
        if {![string is boolean -strict $procinfo(-virtualized)]} {
            lappend mismatches "Invalid value '$procinfo(-virtualized)' for -virtualized field"
        }

        if {(![string is integer -strict $procinfo(-integrity)]) ||
            $procinfo(-integrity) < 0 ||
            $procinfo(-integrity) > 65535} {
            lappend mismatches "Invalid value '$procinfo(-integrity)' for -integrity field"
        }

        if {$procinfo(-integritylabel) ni {untrusted low medium high system}} {
            lappend mismatches "Invalid value '$procinfo(-integritylabel)' for -integritylabel field"
        }

        if {[lsearch -exact {default limited full} $procinfo(-elevation)] < 0} {
            lappend mismatches "Invalid value '$procinfo(-elevation)' for -elevation field"
        }

        # TBD - verify -groups, -groupattrs, -restrictedgroups
        # and -restrictedgroupattrs

        if {! [valid_sids $procinfo(-groupsids)]} {
            lappend mismatches "Invalid groups SIDs"
        }

        # In some cases, like when we are checking ourselves, we do not
        # want to verify all fields since they rapidly change
        if ${verify_all} {
            foreach field {-handlecount
                -pagefilebytes -pagefilebytespeak
                -poolnonpagedbytes -poolpagedbytes
                -threadcount
                -virtualbytes -virtualbytespeak
                -workingset -workingsetpeak} {
                if {![approx [get_process_field $pid $field] $procinfo($field)]} {
                    lappend mismatches "Mismatch in field $field: [get_process_field $pid $field] != $procinfo($field)"
                }
            }
        }

        return [concat [join $mismatches "\n"]]
    }


    # Verify as many thread fields as possible
    proc verify_threadinfo {tid v_threadinfo refresh {verify_all 0}} {
        upvar $v_threadinfo threadinfo

        get_threads $refresh

        set mismatches [list ]
        foreach field {-tid -pid -basepriority} {
            if {[get_thread_field $tid $field] != $threadinfo($field)} {
                lappend mismatches "Mismatch in field $field: [get_thread_field $tid $field] != $threadinfo($field)"
            }
        }

        foreach field {-startaddress} {
            # VB returns truncated 32 bits of 64 bit address. TBD
            if {[get_thread_field $tid $field] != (0xffffffff & $threadinfo($field))} {
                lappend mismatches "Mismatch in field $field: [get_thread_field $tid $field] != $threadinfo($field)"
            }
        }

        if {! [string is integer -strict $threadinfo(-relativepriority)]} {
            lappend mismatches "Field -relativepriority is not an integer."
        }


        # TBD - verify -groups, -groupattrs, -restrictedgroups
        # and -restrictedgroupattrs

        if {! [valid_sids $threadinfo(-groupsids)]} {
            lappend mismatches "Invalid groups SIDs"
        }

        # Following fields are verified only if $verify_all is 1
        # since they are volatile and liable to change, particularly
        # if the process being checked is self
        if {$verify_all} {
            foreach field {-state -priority -waitreason} {
                if {[get_thread_field $tid $field] != $threadinfo($field)} {
                    lappend mismatches "Mismatch in field $field"
                }
            }
        }

        return [concat [join $mismatches "\n"]]
    }


    # Return a PID one more than an existing pid
    proc existing_pid_plus_1 {} {
        # There was a bug in process_exists and get_process_name
        # where they would return true even when process did not exist
        # but the pid was within 3 of an existing process. This
        # proc is used to regression test the fix for the process
        set pid [get_explorer_pid]
        return [incr pid]
    }

    ################################################################

    test get_current_process_id-1.0 {
        get current process id
    } -constraints {
        nt
    } -body {
        twapi::get_current_process_id
    } -result [pid]

    ################################################################

    test get_current_thread_id-1.0 {
        Get current thread id
    } -constraints {
        nt
    } -body {
        expr {[lsearch [get_process_tids [pid]] [twapi::get_current_thread_id]] >= 0}
    } -result 1
    
    
    ################################################################
    
    test get_process_ids-1.0 {
        Get process ids
    } -constraints {
        nt
    } -body {
        get_processes 1
        # At most one process differs (the cscript fired off by get_processes)
        # Note this may sometimes be too conservative causing test to fail
        expr {[llength [::setops::symdiff [array names psinfo] [twapi::get_process_ids]]] <= 1}
    } -result 1
    
    ###

    test get_process_ids-2.0 {
        Get process ids matching our path
    } -constraints {
        nt
    } -body {
        set pids [twapi::get_process_ids -path [info nameofexecutable]]
        expr {[lsearch -exact $pids [pid]] >= 0}
    } -result 1
    
    ###

    test get_process_ids-2.1 {
        Get process ids matching some other process path (native format, lower case)
    } -constraints {
        nt
    } -body {
        get_processes
        set pids [twapi::get_process_ids -path [get_explorer_path]]
        # There should be exactly one explorer
        expr {[llength $pids] == 1 &&
              [verify_pid_name [lindex $pids 0] "explorer.exe"]}
    } -result 1
    
    ###

    test get_process_ids-2.2 {
        Get process ids matching some other process path (unix format, lower case)
    } -constraints {
        nt
    } -body {
        get_processes
        set pids [twapi::get_process_ids -path [file join [get_explorer_path]]]
        # There should be exactly one explorer
        expr {[llength $pids] == 1 &&
              [verify_pid_name [lindex $pids 0] "explorer.exe"]}
    } -result 1
    
    ###
    
    test get_process_ids-2.3 {
        Get process ids matching some other process path (native format, upper case)
    } -constraints {
        nt
    } -body {
        get_processes
        set pids [twapi::get_process_ids -path [string toupper [get_explorer_path]]]
        # There should be exactly one explorer
        expr {[llength $pids] == 1 &&
              [verify_pid_name [lindex $pids 0] "explorer.exe"]}
    } -result 1
    
    ###
    
    test get_process_ids-2.4 {
        Get process ids (empty) matching non-existent path
    } -constraints {
        nt
    } -body {
        get_processes
        # Set up a process path that does not exist
        set explorer_path [file join $::env(WINDIR) "nosuchdir" "explorer.exe"]
        set explorer_path [string tolower [file nativename $explorer_path]]
        set pids [twapi::get_process_ids -path $explorer_path]
        llength $pids
    } -result 0
    
    ###

    test get_process_ids-3.0 {
        Get process id matching our name 
    } -constraints {
        nt
    } -body {
        set pids [twapi::get_process_ids -name [file tail [info nameofexecutable]]]
        expr {[lsearch -exact $pids [pid]] >= 0}
    } -result 1
    
    ###

    test get_process_ids-3.1 {
        Get process ids matching some other process name (lower case)
    } -constraints {
        nt
    } -body {
        get_processes
        set pids [twapi::get_process_ids -name "explorer.exe"]
        # Note there may be more than one explorer if multiple people logged
        verify_pid_name [lindex $pids 0] "explorer.exe"
    } -result 1
    
    ###

    test get_process_ids-3.2 {
        Get process ids matching some other process name (upper case)
    } -constraints {
        nt
    } -body {
        get_processes
        set pids [twapi::get_process_ids -name [string toupper "explorer.exe"]]
        # Note there may be more than one explorer if multiple people logged
        verify_pid_name [lindex $pids 0] "explorer.exe"
    } -result 1
    
    ###

    test get_process_ids-3.3 {
        Get process ids matching some nonexistent name
    } -constraints {
        nt
    } -body {
        get_processes
        llength [twapi::get_process_ids -name "nosuchprocess.exe"]
    } -result 0
    
    ###
    
    test get_process_ids-4.0 {
        Verify -path and -name options are mutually exclusive
    } -constraints {
        nt
    } -body {
        set pids [twapi::get_process_ids -name "nosuchprocess.exe" -path "c:\\nosuchdir\\nosuchprocess.exe"]
    } -returnCodes {error} -result "Options -path and -name are mutually exclusive"
    
    ###
    
    test get_process_ids-5.0 {
        Get process ids using wildcards in process name
    } -constraints {
        nt
    } -body {
        # May have more than one process
        expr {[llength [twapi::get_process_ids -glob -name "explo*.*"]] > 0}
    } -result 1
    
    ###
    
    test get_process_ids-5.1 {
        Get process ids using wildcards in full process path
    } -constraints {
        nt
    } -body {
        # Expect exactly one explorer.exe
        llength [twapi::get_process_ids -glob -path "*:\\*\\explorer.exe"]
    } -result 1
    
    ###
    
    test get_process_ids-5.2 {
        Get process ids using wildcards in partial process path
    } -constraints {
        nt
    } -body {
        # Expect exactly one explorer.exe
        llength [twapi::get_process_ids -glob -path "*\\explorer.exe"]
    } -result 1
    
    ###
    
    test get_process_ids-5.3 {
        Get process ids using wildcards in full process path with Unix separators and upper case
    } -constraints {
        nt
    } -body {
        expr {[llength [twapi::get_process_ids -glob -path "*:/*/EXPLORER.EXE"]] > 0}
    } -result 1
    
    ###
    
    test get_process_ids-5.4 {
        Get process ids using wildcards with nonexistent name
    } -constraints {
        nt
    } -body {
        llength [twapi::get_process_ids -glob -name "*nosuchexe*.*"]
    } -result 0

    ###

    test get_process_ids-5.5 {
        Get process ids using wildcards with nonexistent path
    } -constraints {
        nt
    } -body {
        llength [twapi::get_process_ids -glob -path "*:\\*\\*nosuchexe*.*"]
    } -result 0

    ###

    test get_process_ids-6.0 {
        Get process ids matching a user
    } -constraints {
        nt
    } -setup {
        get_processes
    } -body {
        # Not really an exhaustive test but...
        set pids [twapi::get_process_ids -user $::env(username)]
        set bad_pids [list ]
        foreach pid $pids {
            set user [twapi::kl_get $::psinfo($pid) -user]
            if {$user ne $::env(username)} {
                lappend bad_pids $pid
            }
        }
        set bad_pids
    } -result ""


    ################################################################

    test get_process_modules-1.0 {
        Get handles of modules loaded in a process
    } -body {
        expr {[llength [twapi::get_process_modules [get_explorer_pid] -handle]] > 0}
    } -result 1
    
    test get_process_modules-1.1 {
        Get names of modules loaded in a process
    } -body {
        set mods [twapi::get_process_modules [get_explorer_pid] -name]
        twapi::recordarray cell $mods 0 -name
    } -match path -result explorer.exe
    
    test get_process_modules-1.2 {
        Get paths of modules loaded in a process
    } -body {
        set mods [twapi::get_process_modules [get_explorer_pid] -path]
        twapi::recordarray cell $mods 0 -path
    } -match path -result [get_explorer_path]
    
    test get_process_modules-1.3 {
        Get base of modules loaded in a process
    } -setup {
        if {[info exists ::tcl_platform(pointerSize)] &&
            $::tcl_platform(pointerSize) == 8} {
            set inttype wide
        } else {
            set inttype integer
        }
    } -body {
        # Get all modules and do some minimal checks
        set result ok
        set mods [twapi::get_process_modules [get_explorer_pid] -base]
        foreach val [twapi::recordarray column $mods -base] {
            if {![string is $inttype -strict $val]} {
                set result "Non-integer image base"
                break
            }
        }
        set result
    } -result ok

    test get_process_modules-1.4 {
        Verify -all option returns all attributes of modules
    } -constraints {
        nt
    } -body {
        set mods [twapi::get_process_modules [get_explorer_pid] -all]
        foreach module [twapi::recordarray getlist $mods -format dict] {
            # Verify each field exists 
            # (get_kl_field raises an error if missing)
            get_kl_field $module -handle
            get_kl_field $module -name
            get_kl_field $module -path
            get_kl_field $module -base
            get_kl_field $module -size
            get_kl_field $module -entry
        }
        return ""
    } -result ""

    test get_process_modules-1.5 {
        Get size of modules loaded in a process
    } -setup {
        if {[info exists ::tcl_platform(pointerSize)] &&
            $::tcl_platform(pointerSize) == 8} {
            set inttype wide
        } else {
            set inttype integer
        }
    } -body {
        # Get all modules and do some minimal checks
        set result ok
        set mods [twapi::get_process_modules [get_explorer_pid] -size]
        foreach val [twapi::recordarray column $mods -size] {
            if {![string is $inttype -strict $val]} {
                set result "Non-integer image size"
                break
            }
        }
        set result
    } -result ok

    test get_process_modules-1.6 {
        Get entry of modules loaded in a process
    } -setup {
        if {[info exists ::tcl_platform(pointerSize)] &&
            $::tcl_platform(pointerSize) == 8} {
            set inttype wide
        } else {
            set inttype integer
        }
    } -body {
        # Get all modules and do some minimal checks
        set result ok
        set mods [twapi::get_process_modules [get_explorer_pid] -size]
        foreach val [twapi::recordarray column $mods -size] {
            if {![string is $inttype -strict $val]} {
                set result "Non-integer image size"
                break
            }
        }
        set result
    } -result ok



    ################################################################

    test get_process_path-1.0 {
        Get my process path
    } -constraints {
        nt
    } -body {
        twapi::get_process_path [pid]
    } -match path -result [info nameofexecutable]
    
    ###
    
    test get_process_path-1.1 {
        Get path of some other process
    } -constraints {
        nt
    } -body {
        twapi::get_process_path [get_explorer_pid]
    } -match path -result [get_explorer_path]
    
    ###
    
    test get_process_path-1.2 {
        Get System process path
    } -constraints {
        nt
    } -body {
        twapi::get_process_path [get_system_pid]
    } -result "System"
    
    ###
    
    test get_process_path-1.3 {
        Get Idle process path
    } -constraints {
        nt
    } -body {
        twapi::get_process_path [get_idle_pid]
    } -result "System Idle Process"
    
    ###
    
    test get_process_path-1.4 {
        Get path to non-existent process
    } -constraints {
        nt
    } -body {
        twapi::get_process_path 123
    } -result "(no such process)"
    
    ###
    
    test get_process_path-1.5 {
        Get path to non-existent process using -noexist option
    } -constraints {
        nt
    } -body {
        twapi::get_process_path 123 -noexist "Testing -noexist"
    } -result "Testing -noexist"
    
    ###
    
    test get_process_path-1.6 {
        Get path to inaccessible process
    } -constraints {
        nt
    } -body {
        # Depending on privileges, winlogon may or may not be accessible
        # TBD - figure out a way to run this test with low privs
        twapi::get_process_path [get_winlogon_pid]
    } -match regexp -result {(?i)(.*winlogon.exe)|(\(unknown\))}
    
    ###
    
    test get_process_path-1.7 {
        Get path to inaccessible process using -noaccess option
    } -constraints {
        nt
    } -body {
        # Depending on privileges, winlogon may or may not be accessible
        # TBD - figure out a way to run this test with low privs
        twapi::get_process_path [get_winlogon_pid] \
            -noaccess "no access"
    } -match regexp -result {(?i)(.*winlogon.exe)|(no access)}
    
    
    ################################################################
    
    test get_process_name-1.0 {
        Get my process name
    } -constraints {
        nt
    } -body {
        twapi::get_process_name [pid]
    } -match path -result [file tail [info nameofexecutable]]
    
    ###
    
    test get_process_name-1.1 {
        Get name of some other process
    } -constraints {
        nt
    } -body {
        twapi::get_process_name [get_explorer_pid]
    } -match path -result [file tail [get_explorer_path]]
    
    ###
    
    test get_process_name-1.2 {
        Get System process name
    } -constraints {
        nt
    } -body {
        twapi::get_process_name [get_system_pid]
    } -result "System"
    
    ###
    
    test get_process_name-1.3 {
        Get Idle process name
    } -constraints {
        nt
    } -body {
        twapi::get_process_name [get_idle_pid]
    } -result "System Idle Process"
    
    ###
    
    test get_process_name-1.4 {
        Get name of non-existent process
    } -constraints {
        nt
    } -body {
        twapi::get_process_name 123
    } -result "(no such process)"
    
    ###
    
    test get_process_name-1.5 {
        Get name of non-existent process using -noexist option
    } -constraints {
        nt
    } -body {
        twapi::get_process_name 123 -noexist "Testing -noexist"
    } -result "Testing -noexist"
    
    ###
    
    test get_process_name-1.6 {
        Get name of inaccessible process
    } -constraints {
        nt
    } -body {
        # Depending on privileges, winlogon may or may not be accessible
        # TBD - figure out a way to run this test with low privs
        twapi::get_process_name [get_winlogon_pid]
    } -match regexp -result {(?i)(.*winlogon.exe)|(\(unknown\))}
    
    ###
    
    test get_process_name-1.7 {
        Get name to inaccessible process using -noaccess option
    } -constraints {
        nt
    } -body {
        # Depending on privileges, winlogon may or may not be accessible
        # TBD - figure out a way to run this test with low privs
        twapi::get_process_name [get_winlogon_pid] \
            -noaccess "no access"
    } -match regexp -result {(?i)(.*winlogon.exe)|(no access)}
    
    ###
    
    test get_process_name-1.4 {
        Get name of non-existent process whose PID is within 3 of existing process
    } -constraints {
        nt
    } -body {
        twapi::get_process_name [existing_pid_plus_1]
    } -result "(no such process)"
    
    ################################################################
    
    test get_device_drivers-1.0 {
        get_device_drivers with no arguments
    } -constraints {
        nt
    } -body {
        twapi::recordarray size [twapi::get_device_drivers]
    } -result 0

    ###

    test get_device_drivers-1.1 {
        Get load addresses of device drivers
    } -constraints {
        nt
    } -body {
        verify_list_kl_fields [twapi::recordarray getlist [twapi::get_device_drivers -base] -format dict] {-base}
    } -result ""
    
    ###
    
    test get_device_drivers-1.2 {
        Get names of device drivers
    } -constraints {
        nt
    } -body {
        verify_list_kl_fields [twapi::recordarray getlist [twapi::get_device_drivers -name] -format dict] {-name}
    } -result ""
    
    ###
    
    test get_device_drivers-1.3 {
        Get paths of device drivers
    } -constraints {
        nt
    } -body {
        verify_list_kl_fields [twapi::recordarray getlist [twapi::get_device_drivers -path] -format dict] {-path}
    } -result ""

    ###

    test get_device_drivers-1.4 {
        Get all attributes of device drivers
    } -constraints {
        nt
    } -body {
        verify_list_kl_fields [twapi::recordarray getlist [twapi::get_device_drivers -all] -format dict] {-path -name -base}
    } -result ""
    


    ################################################################
    
    test process_exists-1.0 {
        Check our process exists
    } -constraints {
        nt
    } -body {
        twapi::process_exists [pid]
    } -result 1
    
    ###
    
    test process_exists-1.1 {
        Check some other process exists
    } -constraints {
        nt
    } -body {
        twapi::process_exists [get_explorer_pid]
    } -result 1
    
    ###
    
    test process_exists-1.2 {
        Check for non-existent process
    } -constraints {
        nt
    } -body {
        twapi::process_exists 123
    } -result 0
    
    ###
    
    test process_exists-1.3 {
        Check for PID one more than the pid of an existing process
    } -constraints {
        nt
    } -body {
        twapi::process_exists [existing_pid_plus_1]
    } -result 0
    
    ###
    
    test process_exists-2.0 {
        Check process exists and has the specified name (yes)
    } -constraints {
        nt
    } -body {
        twapi::process_exists [get_explorer_pid] -name "explorer.exe"
    } -result 1
    
    ###
    
    test process_exists-2.1 {
        Check process exists and has the specified name (no)
    } -constraints {
        nt
    } -body {
        twapi::process_exists [get_explorer_pid] -name "dummy.exe"
    } -result 0
    
    ###
    
    test process_exists-2.2 {
        Check process exists and matches the specified glob name when -glob specified
    } -constraints {
        nt
    } -body {
        twapi::process_exists [get_explorer_pid] \
            -glob -name "explo*.*"
    } -result 1
    
    ###
    
    test process_exists-2.3 {
        Check existing process does not match glob pattern if -glob not specified
    } -constraints {
        nt
    } -body {
        twapi::process_exists [get_explorer_pid] -name "explo*.*"
    } -result 0
    
    ###
    
    test process_exists-2.4 {
        Check non-existent pid does not match an existing process of the specified name
    } -constraints {
        nt
    } -body {
        twapi::process_exists 123 -name "explorer.exe"
    } -result 0
    
    ###
    
    test process_exists-3.0 {
        Check process exists and has the specified path (yes)
    } -constraints {
        nt
    } -body {
        twapi::process_exists [get_explorer_pid] \
            -path [get_explorer_path]
    } -result 1
    
    ###
    
    test process_exists-3.1 {
        Check process exists and has the specified path (no)
    } -constraints {
        nt
    } -body {
        twapi::process_exists [get_explorer_pid] \
            -path [get_winlogon_path]
    } -result 0
    
    ###
    
    test process_exists-3.2 {
        Check process exists and matches the specified glob path
    } -constraints {
        nt
    } -body {
        twapi::process_exists [get_explorer_pid] -glob \
            -path [file join [file dirname [get_explorer_path]] "*.*"]
    } -result 1
    
    ###
    
    test process_exists-3.3 {
        Check existing process does not match the glob path if -glob not specified
    } -constraints {
        nt
    } -body {
        twapi::process_exists [get_explorer_pid] \
            -path [file join [file dirname [get_explorer_path]] "*.*"]
    } -result 0
    
    ###
    
    test process_exists-3.4 {
        Check non-existent pid does not match an existing process of the specified path
    } -constraints {
        nt
    } -body {
        twapi::process_exists 123 -path [get_explorer_path]
    } -result 0
    
    ################################################################
    
    test end_process-1.0 {
        Verify unforced termination of a process
    } -constraints {
        nt
    } -body {
        set notepad_pid [notepad_exec]
        twapi::end_process $notepad_pid
        after 100
        twapi::process_exists $notepad_pid
    } -result 0

    test end_process-1.1 {
        Verify forced termination of a process
    } -constraints {
        nt
    } -body {
        set notepad_pid [notepad_exec_and_insert]

        # Now terminate without a force - it should not work since
        # the notepad will pop up a confirmation window waiting for user
        # response
        twapi::end_process $notepad_pid -wait 1000
        if {![twapi::process_exists $notepad_pid]} {
            error "Process ended without -force"
        }
        # Now forcibly terminate
        twapi::end_process $notepad_pid -force -wait 500 -exitcode 3
        # TBD - how can check the exit code for the process?
        after 100
        twapi::process_exists $notepad_pid
    } -result 0


    ################################################################

    test get_thread_parent_process_id-1.0 {
        Get process id of current thread
    } -constraints {
        nt
    } -body {
        twapi::get_thread_parent_process_id [twapi::get_current_thread_id]
    } -result [pid]

    ###

    test get_thread_parent_process_id-1.1 {
        Get process id of a thread in some other process
    } -constraints {
        nt
    } -body {
        get_threads [get_explorer_pid]
        twapi::get_thread_parent_process_id [lindex [get_process_tids [get_explorer_pid]] 0]
    } -result [get_explorer_pid]

    ################################################################

    test get_process_thread_ids-1.0 {
        Get list of thread ids in a process
    } -constraints {
        nt
    } -body {
        get_threads [get_explorer_pid]
        set pid [get_explorer_pid]
        setops::symdiff [twapi::get_process_thread_ids $pid] [get_process_tids $pid]
    } -result ""

    ################################################################

    test get_process_info-1.0 {
        Verify get_process_info with no options specified
    } -constraints {
        nt
    } -body {
        twapi::get_process_info [pid]
    } -result ""

    ###

    test get_process_info-2.0 {
        Verify get_process_info -all for this process
    } -constraints {
        nt
    } -body {
        # Note we do not verify all fields since they change as we run
        array set procinfo [twapi::get_process_info [pid] -all]
        verify_get_process_info_fieldnames [array get procinfo]
        verify_processinfo [pid] procinfo 0 0
    } -result ""

    ###

    test get_process_info-2.1 {
        Verify get_process_info -all for another process
    } -constraints {
        nt
    } -body {
        set pid [get_explorer_pid]
        array set procinfo [twapi::get_process_info $pid -all]
        # We verify *all* fields, assuming they will not change for a different
        # process while we are running
        verify_processinfo $pid procinfo 1 1
    } -result ""
    

    ###

    test get_process_info-2.2 {
        Verify retrieval of information for non-existent processes
    } -constraints {
        nt
    } -body {
        lindex [twapi::get_process_info 123 -name] 1
    } -result "(no such process)"

    ###

    test get_process_info-2.3 {
        Verify retrieval of -all for non-existent processes
    } -constraints {
        nt
    } -body {
        set values [twapi::get_process_info 123 -all]
        verify_get_process_info_fieldnames $values
        set bad [list ]
        foreach {fld val} $values {
            if {$fld eq "-pid"} {
                if {$val != 123} {
                    lappend bad $fld
                }
            } else {
                if {$val ne "(no such process)"} {
                    lappend bad $fld
                }
            }
        }
        set bad
    } -result ""

    ###

    test get_process_info-2.4 {
        Verify -noexist option when retrieving information for non-existent processes
    } -constraints {
        nt
    } -body {
        lindex [twapi::get_process_info 123 -name -noexist foobar ] 1
    } -result "foobar"

    ## Option tests begin

    test get_process_info-4.0 {
        Verify get_process_info -logonsession for another process
    } -constraints {
        nt
    } -body {
        # TBD -need a way to verify these other than just syntax
        eval set [twapi::get_process_info [get_explorer_pid] -logonsession]
        set -logonsession
    } -result {^[[:xdigit:]]{8}-[[:xdigit:]]{8}$} -match regexp

    
    test get_process_info-5.0 {
        Verify get_process_info -virtualized for another process
    } -constraints {
        nt TBD
    } -body {
        Note virtualization is not enabled when running under administrator ?
        Would need to fire up a process a another user ?
    } -result ""

    test get_process_info-6.0 {
        Verify get_process_info -elevation for another process
    } -constraints {
        nt
    } -setup {
        set np_pid [notepad_exec]
    } -body {
        twapi::get_process_info $np_pid -elevation
    } -cleanup {
        twapi::end_process $np_pid -wait 500
    } -match oneof -result {{-elevation default} {-elevation full} {-elevation limited}}

    test get_process_info-7.0 {
        Verify get_process_info -integrity for another process
    } -constraints {
        nt
    } -setup {
        set np_pid [notepad_exec]
    } -body {
        twapi::get_process_info $np_pid -integrity
    } -cleanup {
        twapi::end_process $np_pid -wait 500
    } -match regexp -result {^-integrity\s+[[:digit:]]{1,5}$}

    test get_process_info-8.0 {
        Verify get_process_info -integritylabel for another process
    } -constraints {
        nt
    } -setup {
        set np_pid [notepad_exec]
    } -body {
        twapi::get_process_info $np_pid -integritylabel
    } -cleanup {
        twapi::end_process $np_pid -wait 500
    } -match oneof -result {{-integritylabel untrusted} {-integritylabel low} {-integritylabel medium} {-integritylabel high} {-integritylabel system}}

    test get_process_info-9.0 {
        Verify get_process_info -primarygroup and -primarygroupsid for another process
    } -setup {
        set np_pid [notepad_exec]
    } -body {
        set grp [twapi::get_process_info $np_pid -primarygroup]
        set sid [twapi::get_process_info $np_pid -primarygroupsid]
        list [lindex $grp 0] [lindex $sid 0] [string equal [twapi::map_account_to_sid [lindex $grp 1]] [lindex $sid 1]]
    } -cleanup {
        twapi::end_process $np_pid -wait 500
    } -result {-primarygroup -primarygroupsid 1}

    test get_process_info-10.0 {
        Verify get_process_info -privileges, -enabledprivilges and -disabledprivileges
    } -setup {
        set np_pid [notepad_exec]
    } -body {
        set privs [twapi::get_process_info $np_pid -privileges]
        set enabled [twapi::get_process_info $np_pid -enabledprivileges]
        set disabled [twapi::get_process_info $np_pid -disabledprivileges]
        set enabled_privs  [lindex $enabled 1]
        set disabled_privs [lindex $disabled 1]

        list [lindex $privs 0] [lindex $enabled 0] [lindex $disabled 0] \
            [equal_sets [lindex $privs 1 0] $enabled_privs]  \
            [equal_sets [lindex $privs 1 1] $disabled_privs] \
            [llength [setops::intersect $enabled_privs $disabled_privs]] \
            [valid_privs $enabled_privs] \
            [valid_privs $disabled_privs]
    } -cleanup {
        twapi::end_process $np_pid -wait 500
    } -result {-privileges -enabledprivileges -disabledprivileges 1 1 0 1 1}

    test get_process_info-11.0 {
        Verify get_process_info -groupsids and -groupattrs for another process
    } -setup {
        set np_pid [notepad_exec]
    } -body {
        set grps [twapi::get_process_info $np_pid -groupsids]
        set grpattrs [twapi::get_process_info $np_pid -groupattrs]
        set grps2 {}
        set bad_attrs {}

        foreach {sid attrs} [lindex $grpattrs 1] {
            lappend grps2 $sid
            foreach attr $attrs {
                if {$attr ni {}} {
                    lappend bad_attrs $attr
                }
            }
        }
        list [lindex $grps 0] [lindex $grpattrs 0] [equal_sets [lindex $grps 1] $grps2]
    } -cleanup {
        twapi::end_process $np_pid -wait 500
    } -result {-groupsids -groupattrs 1}

    test get_process_info-12.0 {
        Verify get_process_info -restrictedgroups and -restrictedgroupattrs for another process
    } -setup {
        # TBD - how to set up restricted groups for a process to test this ?
        set np_pid [notepad_exec]
    } -body {
        set grps [twapi::get_process_info $np_pid -restrictedgroups]
        set grpattrs [twapi::get_process_info $np_pid -restrictedgroupattrs]
        set grps2 {}
        set bad_attrs {}
        foreach {sid attrs} [lindex $grpattrs 1] {
            lappend grps2 [twapi::map_account_to_name $sid]
            foreach attr $attrs {
                if {$attr ni {}} {
                    lappend bad_attrs $attr
                }
            }
        }
        list [lindex $grps 0] [lindex $grpattrs 0] [equal_sets [lindex $grps 1] $grps2]
    } -cleanup {
        twapi::end_process $np_pid -wait 500
    } -result {-restrictedgroups -restrictedgroupattrs 1}

    test get_process_info-13.0 {
        Verify get_process_info -groups
    } -setup {
        set np_pid [notepad_exec]
    } -body {
        lassign [twapi::get_process_info $np_pid -groups] opt grps
        set ninvalid 0
        foreach grp $grps {
            if {[catch {twapi::map_account_to_sid $grp}] &&
                ![twapi::is_valid_sid_syntax $grp] &&
                ![string match -nocase logonsessionid_* $grp]} {
                puts $grp
                incr ninvalid
            }
        }
        list $opt [expr {[llength $grps] > 0}] $ninvalid
    } -cleanup {
        twapi::end_process $np_pid -wait 500
    } -result {-groups 1 0}

    set testnum 13

    ### Option tests - path type fields
    foreach field {-name -path} {
        test get_process_info-[incr testnum].0 "Verify get_process_info $field for another process" -body "
            set pid \[get_explorer_pid\]
            eval set \[twapi::get_process_info \$pid $field\]
            set checkval \[get_process_field \$pid $field\]
            set procval \[set $field\]
            if {\[equal_paths \$checkval \$procval \]} {
                return {}
            } else {
                return \"Mismatch in field $field (\$procval != \$checkval)\"
            }
        " -result ""
    }

    # Also try for ourselves since that takes a different path
    test get_process_info-[incr testnum].0 {
        Verify get_process_info -path for our process
    } -body {
        twapi::get_process_info [pid] -path
    } -result [list -path [info nameofexecutable]]

    test get_process_info-[incr testnum].0 {
        Verify get_process_info -name for our process
    } -body {
        twapi::get_process_info [pid] -name
    } -result [list -name [file tail [info nameofexecutable]]]


    ### Option tests - numeric type fields - exact match

    foreach field {-basepriority -parent -pid -threadcount} {
        test get_process_info-[incr testnum].0 "Verify get_process_info $field for another process" -constraints {
            nt
        } -body "
            set pid \[get_explorer_pid\]
            eval set \[twapi::get_process_info \$pid $field\]
            set checkval \[get_process_field \$pid $field\]
            set procval \[set $field\]
            if {\$checkval == \$procval} {
                return {}
            } else {
                return \"Mismatch in field $field (\$procval != \$checkval)\"
            }
        " -result ""
    }

    ### Option tests - numeric type fields - approximate match
    # Note when comparing values, the WMI code to get values is potentially
    # scaled on different platforms. Hence we pass 1024 as a potential
    # adjustment factor to the approx command.

    foreach field { -handlecount
        -pagefilebytes -pagefilebytespeak
        -poolnonpagedbytes -poolpagedbytes
        -virtualbytes -virtualbytespeak -workingset -workingsetpeak} {
        test get_process_info-[incr testnum].0 "Verify get_process_info $field for another process" -constraints {
            nt
        } -body "
            set pid \[get_explorer_pid\]
            eval set \[twapi::get_process_info \$pid $field\]
            set checkval \[get_process_field \$pid $field\]
            set procval \[set $field\]
            if {\[approx \$checkval \$procval\ 1024]} {
                return {}
            } else {
                return \"Mismatch in field $field (\$procval != \$checkval)\"
            }
        " -result ""
    }


    ### Option tests - fields that cannot be verified but are typed

    foreach {field type} {
        -elapsedtime       wide
        -ioreadops         wide
        -iowriteops        wide
        -iootherops        wide
        -ioreadbytes       wide
        -iowritebytes      wide
        -iootherbytes      wide
        -priorityclass     wide
    } {
        test get_process_info-[incr testnum].0 "Verify get_process_info $field for another process" -constraints {
            nt
        } -body "
            set pid \[get_explorer_pid\]
            eval set \[twapi::get_process_info \$pid $field\]
            string is $type \[set $field\]
        " -result 1
    }

    ################################################################
    
    test get_thread_info-1.0 {
        Verify get_thread_info with no options specified
    } -constraints {
        nt
    } -body {
        twapi::get_thread_info [twapi::get_current_thread_id]
    } -result ""
    
    ###

    test get_thread_info-2.0 {
        Verify get_thread_info -all for this thread
    } -constraints {
        nt
    } -body {
        set tid [twapi::get_current_thread_id]
        array set threadinfo [twapi::get_thread_info $tid -all]
        verify_threadinfo $tid threadinfo 0 0
    } -result ""

    ###

    test get_thread_info-2.1 {
        Verify get_thread_info -all for another process
    } -constraints {
        nt
    } -body {
        set tid [get_explorer_tid]
        array set threadinfo [twapi::get_thread_info $tid -all]
        verify_threadinfo $tid threadinfo 1 1
    } -result ""

    ###

    set testnum 0
    foreach field {-tid -basepriority -pid -priority -state -waitreason} {
        test get_thread_info-3.$testnum "Verify get_thread_info $field" -constraints {
            nt
        } -body "
            set tid \[get_explorer_tid\]
            eval set \[twapi::get_thread_info \$tid $field\]
            expr {\[get_thread_field \$tid $field\] == \[set $field\]}
        " -result 1
        incr testnum
    }

    # Need to truncate when comparing some fields because VB returns 32-bit
    # addresses on 64-bit systems
    foreach field {-startaddress} {
        test get_thread_info-3.$testnum "Verify get_thread_info $field" -constraints {
            nt
        } -body "
            set tid \[get_explorer_tid\]
            eval set \[twapi::get_thread_info \$tid $field\]
            expr {\[get_thread_field \$tid $field\] == (0xffffffff & \[set $field\])}
        " -result 1
        incr testnum
    }


    ### Option tests - fields that cannot be verified but should be numeric

    foreach {field type} {
        -elapsedtime      integer
        -contextswitches  integer
        -relativepriority integer
    } {
        test get_thread_info-3.$testnum "Verify get_thread_info $field" -constraints {
            nt
        } -body "
            set tid \[get_explorer_tid\]
            eval set \[twapi::get_thread_info \$tid $field\]
            string is $type \[set $field\]
        " -result 1
        incr testnum
    }

    ###

    test get_thread_info-4.0 {
        Verify retrieval of information for non-existent thread
    } -constraints {
        nt
    } -body {
        lindex [twapi::get_thread_info 123 -user] 1
    } -result "(no such thread)"

    ###

    test get_thread_info-4.1 {
        Verify retrieval of -all for non-existent thread
    } -constraints {
        nt
    } -body {
        set values [twapi::get_thread_info 123 -all]
        set bad [list ]
        foreach {fld val} $values {
            if {$fld eq "-tid"} {
                if {$val != 123} {
                    lappend bad $fld
                }
            } else {
                if {$val ne "(no such thread)"} {
                    lappend bad $fld
                }
            }
        }
        set bad
    } -result ""

    ###

    test get_thread_info-4.2 {
        Verify -noexist option when retrieving information for non-existent thread
    } -constraints {
        nt
    } -body {
        lindex [twapi::get_thread_info 123 -user -noexist foobar ] 1
    } -result "foobar"


    test get_thread_info-5.0 {
        Verify get_thread_info -groupsids and -groupattrs for a thread
    } -body {
        set tid [twapi::get_current_thread_id]
        set grps [twapi::get_thread_info $tid -groupsids]
        set grpattrs [twapi::get_thread_info $tid -groupattrs]
        set grps2 {}
        set bad_attrs {}
        foreach {sid attrs} [lindex $grpattrs 1] {
            lappend grps2 $sid
            foreach attr $attrs {
                if {$attr ni {}} {
                    lappend bad_attrs $attr
                }
            }
        }
        list [lindex $grps 0] [lindex $grpattrs 0] [equal_sets [lindex $grps 1] $grps2]
    } -result {-groupsids -groupattrs 1}

    test get_thread_info-6.0 {
        Verify get_thread_info -restrictedgroups and -restrictedgroupattrs for a thread
    } -body {
        set tid [twapi::get_current_thread_id]
        set grps [twapi::get_thread_info $tid -restrictedgroups]
        set grpattrs [twapi::get_thread_info $tid -restrictedgroupattrs]
        set grps2 {}
        set bad_attrs {}
        foreach {sid attrs} [lindex $grpattrs 1] {
            lappend grps2 [twapi::map_account_to_name $sid]
            foreach attr $attrs {
                if {$attr ni {}} {
                    lappend bad_attrs $attr
                }
            }
        }
        list [lindex $grps 0] [lindex $grpattrs 0] [equal_sets [lindex $grps 1] $grps2]
    } -result {-restrictedgroups -restrictedgroupattrs 1}

    test get_thread_info-7.0 {
        Verify get_thread_info -groups
    } -setup {
        set tid [twapi::get_current_thread_id]
    } -body {
        lassign [twapi::get_thread_info $tid -groups] opt grps
        set ninvalid 0
        foreach grp $grps {
            if {[catch {twapi::map_account_to_sid $grp}] &&
                ![twapi::is_valid_sid_syntax $grp] &&
                ![string match -nocase logonsessionid_* $grp]} {
                puts $grp
                incr ninvalid
            }
        }
        list $opt [expr {[llength $grps] > 0}] $ninvalid
    } -result {-groups 1 0}

    ################################################################

    test create_process-1.0 {
        Verify create_process using a path
    } -constraints {
        nt
    } -body {
        set result [twapi::create_process [get_notepad_path]]
        if {[llength $result] != 2} {
            error "Create process returned [llength $result] elements"
        }
        set pid [lindex $result 0]
        if {![twapi::process_waiting_for_input $pid -wait 1000]} {
            error "Could not start process"
        }
        twapi::end_process $pid
        set result ""
    } -result ""

    test create_process-1.1 {
        Verify create_process using a path and a junk command line
    } -constraints {
        nt
    } -body {
        set result [twapi::create_process [get_notepad_path] -cmdline "JUNK process.test"]
        if {[llength $result] != 2} {
            error "Create process returned [llength $result] elements"
        }
        set pid [lindex $result 0]
        set success [twapi::process_waiting_for_input $pid -wait 1000]
        twapi::end_process $pid
        set success
    } -result 1

    ###

    test create_process-2.0 {
        Verify create_process using a command line
    } -constraints {
        nt
    } -body {
        set result [twapi::create_process "" -cmdline "\"[get_notepad_path]\""]
        if {[llength $result] != 2} {
            error "Create process returned [llength $result] elements"
        }
        set pid [lindex $result 0]
        if {![twapi::process_waiting_for_input $pid -wait 1000]} {
            error "Could not start process"
        }
        twapi::end_process $pid
        set result ""
    } -result ""

    ###

    test create_process-2.1 {
        Verify create_process using a command line with arguments
    } -constraints {
        nt
    } -body {
        set result [twapi::create_process "" -cmdline "\"[get_notepad_path]\" process.test"]
        if {[llength $result] != 2} {
            error "Create process returned [llength $result] elements"
        }
        set pid [lindex $result 0]
        if {![twapi::process_waiting_for_input $pid -wait 1000]} {
            error "Could not start process"
        }
        
        set hwins [twapi::find_windows -toplevel true -match glob -text "process.test*" -pids $pid]
        twapi::end_process $pid

        llength $hwins
    } -result 1

    ###

    test create_process-3.0 {
        Verify create_process -createsuspended 1
    } -constraints {
        nt win2k
    } -body {
        set result [twapi::create_process [get_notepad_path] -createsuspended 1 -returnhandles 1]
        if {[llength $result] != 4} {
            error "Create process returned [llength $result] elements"
        }
        foreach {pid tid phnd thnd} $result {break}
        if {[twapi::process_waiting_for_input $pid -wait 1000]} {
            error "Created process not suspended"
        }
        twapi::ResumeThread $thnd

        twapi::close_handles $thnd $phnd
        if {![twapi::process_waiting_for_input $pid -wait 1000]} {
            error "Suspended process not resumed"
        }

        set hwins [twapi::find_windows -toplevel true -pids $pid]
        twapi::end_process $pid

        expr {[llength $hwins] > 0}
    } -result 1

    ###

    test create_process-3.1 {
        Verify create_process -createsuspended 0
    } -constraints {
        nt win2k
    } -body {
        set result [twapi::create_process [get_notepad_path] -createsuspended 0 -returnhandles 1]
        if {[llength $result] != 4} {
            error "Create process returned [llength $result] elements"
        }
        foreach {pid tid phnd thnd} $result {break}
        if {![twapi::process_waiting_for_input $pid -wait 1000]} {
            error "Process creation failed or process suspended"
        }

        twapi::close_handles $thnd $phnd

        set hwins [twapi::find_windows -toplevel true -pids $pid]
        twapi::end_process $pid

        expr {[llength $hwins] > 0}
    } -result 1

    ###

    test create_process-4.0 {
        Verify create_process -title
    } -constraints {
        nt
    } -body {
        set result [twapi::create_process [get_cmd_path] -newconsole 1 -title TWAPI]
        if {[llength $result] != 2} {
            error "Create process returned [llength $result] elements"
        }
        set pid [lindex $result 0]
        after 50;                       # Wait for console process to come up
        
        set hwins [twapi::find_windows -text "*TWAPI" -match glob -pids $pid]
        twapi::end_process $pid

        llength $hwins
    } -result 1

    ###

    test create_process-5.0 {
        Verify create_process -windowpos {X Y}
    } -constraints {
        nt
    } -body {
        set xoff 200
        set yoff 100
        set result [twapi::create_process [get_cmd_path] -windowpos "$xoff $yoff" -newconsole 1]
        if {[llength $result] != 2} {
            error "Create process returned [llength $result] elements"
        }
        # Give time to start. wait_for_input_idle is ineffective with console
        after 100;
        set pid [lindex $result 0]
        set hwin [lindex [twapi::find_windows -toplevel true -pids $pid] 0]
        if {[llength $hwin] == 0} {
            after 100
            set hwin [lindex [twapi::find_windows -toplevel true -pids $pid] 0]
        }
        foreach {topx topy botx boty} [twapi::get_window_coordinates $hwin] break
        twapi::end_process $pid

        expr {$topx == $xoff && $topy == $yoff}
    } -result 1

    ###

    test create_process-5.1 {
        Verify create_process -windowpos X,Y
    } -constraints {
        nt
    } -body {
        set xoff 200
        set yoff 100
        set result [twapi::create_process [get_cmd_path] -windowpos "$xoff,$yoff" -newconsole 1]
        # Give time to start. wait_for_input_idle is ineffective with console
        after 100
        if {[llength $result] != 2} {
            error "Create process returned [llength $result] elements"
        }
        set pid [lindex $result 0]
        set hwin [lindex [twapi::find_windows -toplevel true -pids $pid] 0]
        foreach {topx topy botx boty} [twapi::get_window_coordinates $hwin] break
        twapi::end_process $pid

        expr {$topx == $xoff && $topy == $yoff}
    } -result 1

    ###

    test create_process-6.0 {
        Verify create_process -windowsize {X Y}
    } -constraints {
        nt
    } -body {
        set xsize 200
        set ysize 100
        set result [twapi::create_process [get_cmd_path] -windowsize "$xsize $ysize" -newconsole 1]
        if {[llength $result] != 2} {
            error "Create process returned [llength $result] elements"
        }
        set pid [lindex $result 0]
        # Give time to start. wait_for_input_idle is ineffective with console
        for {set i 0} {$i < 20} {incr i} {
            set hwin [lindex [twapi::find_windows -toplevel true -pids $pid] 0]
            if {$hwin ne ""} break
            after 50
        }
        if {$hwin eq ""} {
            error "Could not find window for spawned cmd"
        }
        foreach {x y} [twapi::get_window_client_area_size $hwin] break
        twapi::end_process $pid
        # Size may not be exact because the window may be slightly off
        # to account for whole characters
        expr {$x > ($xsize-10) && $x < ($xsize+10) &&
              $y > ($ysize-10) && $y < ($ysize+10)}
    } -result 1

    ###

    test create_process-6.1 {
        Verify create_process -windowsize X,Y
    } -constraints {
        nt
    } -body {
        set xsize 200
        set ysize 100
        set result [twapi::create_process [get_cmd_path] -windowsize "$xsize,$ysize" -newconsole 1]
        if {[llength $result] != 2} {
            error "Create process returned [llength $result] elements"
        }
        set pid [lindex $result 0]
        # Give time to start. wait_for_input_idle is ineffective with console
        for {set i 0} {$i < 20} {incr i} {
            set hwin [lindex [twapi::find_windows -toplevel true -pids $pid] 0]
            if {$hwin ne ""} break
            after 50
        }
        if {$hwin eq ""} {
            error "Could not find window for spawned cmd"
        }
        foreach {x y} [twapi::get_window_client_area_size $hwin] break
        twapi::end_process $pid
        # Size may not be exact because the window may be slightly off
        # to account for whole characters
        expr {$x > ($xsize-10) && $x < ($xsize+10) &&
              $y > ($ysize-10) && $y < ($ysize+10)}
    } -result 1

    ###

    test create_process-7.0 {
        Verify create_process -showwindow normal
    } -constraints {
        nt
    } -body {
        set result [twapi::create_process [get_notepad_path] -showwindow normal]
        if {[llength $result] != 2} {
            error "Create process returned [llength $result] elements"
        }
        set pid [lindex $result 0]
        if {![twapi::process_waiting_for_input $pid -wait 1000]} {
            error "Could not start process"
        }
        set hwin [lindex [twapi::find_windows -toplevel true -match glob -text "Untitled*" -pids $pid] 0]
        set result [expr {[twapi::window_visible $hwin] &&
                          (![twapi::window_maximized $hwin]) &&
                          (![twapi::window_minimized $hwin])}]
        twapi::end_process $pid

        set result
    } -result 1

    ###

    test create_process-7.1 {
        Verify create_process -showwindow hidden
    } -constraints {
        nt
    } -body {
        set result [twapi::create_process [get_notepad_path] -showwindow hidden]
        if {[llength $result] != 2} {
            error "Create process returned [llength $result] elements"
        }
        set pid [lindex $result 0]
        if {![twapi::process_waiting_for_input $pid -wait 1000]} {
            error "Could not start process"
        }
        set hwin [lindex [twapi::find_windows -toplevel true -match glob -text "Untitled*" -pids $pid] 0]
        set result [twapi::window_visible $hwin]
        twapi::end_process $pid

        set result
    } -result 0

    test create_process-7.2 {
        Verify create_process -showwindow minimized
    } -constraints {
        nt
    } -body {
        set result [twapi::create_process [get_notepad_path] -showwindow minimized]
        if {[llength $result] != 2} {
            error "Create process returned [llength $result] elements"
        }
        set pid [lindex $result 0]
        if {![twapi::process_waiting_for_input $pid -wait 1000]} {
            error "Could not start process"
        }
        set hwin [lindex [twapi::find_windows -toplevel true -match glob -text "Untitled*" -pids $pid] 0]
        set result [expr {[twapi::window_visible $hwin] &&
                          [twapi::window_minimized $hwin]}]
        twapi::end_process $pid

        set result
    } -result 1

    ###

    test create_process-7.3 {
        Verify create_process -showwindow maximized
    } -constraints {
        nt
    } -body {
        set result [twapi::create_process [get_notepad_path] -showwindow maximized]
        if {[llength $result] != 2} {
            error "Create process returned [llength $result] elements"
        }
        set pid [lindex $result 0]
        if {![twapi::process_waiting_for_input $pid -wait 1000]} {
            error "Could not start process"
        }
        set hwin [lindex [twapi::find_windows -toplevel true -match glob -text "Untitled*" -pids $pid] 0]
        set result [expr {[twapi::window_visible $hwin] &&
                          [twapi::window_maximized $hwin]}]
        twapi::end_process $pid

        set result
    } -result 1

    test create_process-8.0 {
        create_process -stdchannels pipe
    } -setup {
        lassign [chan pipe] parentread childwrite
        lassign [chan pipe] childread parentwrite
    } -body {
        twapi::create_process $::env(COMSPEC) -inherithandles 1 -stdchannels [list $childread $childwrite $childwrite]
        close $childwrite
        close $childread
        puts $parentwrite "VER"
        puts $parentwrite "exit"
        flush $parentwrite
        read $parentread
    } -cleanup {
        close $parentwrite
        close $parentread
    } -result {*Microsoft Windows *Version*VER*Microsoft Windows *Version*exit*} -match glob

    test create_process-8.1 {
        create_process -stdchannels redirect output to file
    } -setup {
        set tempfile [write_test_file ""]
        set outfd [open $tempfile w]
        lassign [chan pipe] childread parentwrite
    } -body {
        lassign [twapi::create_process $::env(COMSPEC) -inherithandles 1 -stdchannels [list $childread $outfd $outfd] -returnhandles 1] pid tid hpid htid
        close $outfd
        close $childread
        twapi::close_handle $htid
        puts $parentwrite "VER"
        puts $parentwrite "exit"
        flush $parentwrite
        twapi::wait_on_handle $hpid
        read_file  $tempfile
    } -cleanup {
        close $parentwrite
    } -result {*Microsoft Windows *Version*VER*Microsoft Windows *Version*exit*} -match glob


    test create_process-9.0 {
        create_process -stdhandles pipe
    } -setup {
        lassign [chan pipe] parentread childwrite
        lassign [chan pipe] childread parentwrite
    } -body {
        set hread [twapi::get_tcl_channel_handle $childread read]
        set hwrite [twapi::get_tcl_channel_handle $childwrite write]
        twapi::set_handle_inheritance $hread 1
        twapi::set_handle_inheritance $hwrite 1
        twapi::create_process $::env(COMSPEC) -inherithandles 1 -stdhandles [list $hread $hwrite $hwrite]
        close $childwrite
        close $childread
        puts $parentwrite "VER"
        puts $parentwrite "exit"
        flush $parentwrite
        read $parentread
    } -cleanup {
        close $parentwrite
        close $parentread
    } -result {*Microsoft Windows *Version*VER*Microsoft Windows *Version*exit*} -match glob

    test create_process-9.1 {
        create_process -stdhandles redirect output
    } -setup {
        set tempfile [write_test_file ""]
        set outfd [open $tempfile w]
        lassign [chan pipe] childread parentwrite
    } -body {
        set hread [twapi::get_tcl_channel_handle $childread read]
        set hwrite [twapi::get_tcl_channel_handle $outfd write]
        twapi::set_handle_inheritance $hread 1
        twapi::set_handle_inheritance $hwrite 1
        lassign [twapi::create_process $::env(COMSPEC) -inherithandles 1 -stdhandles [list $hread $hwrite $hwrite] -returnhandles 1] pid tid hpid htid
        close $childread
        close $outfd
        puts $parentwrite "VER"
        puts $parentwrite "exit"
        flush $parentwrite
        twapi::wait_on_handle $hpid
        read_file $tempfile
    } -cleanup {
        close $parentwrite
    } -result {*Microsoft Windows *Version*VER*Microsoft Windows *Version*exit*} -match glob


    test create_process-99.0 {
        TBD - add tests for all create_process options
    } -constraints {
        TBD 
    } -body {
    } -result ""

    ################################################################

    test get_priority_class-1.0 {
        Get our own priority class
    } -constraints {
        nt
    } -body {
        twapi::get_priority_class [pid]
    } -result 32

    ###

    test get_priority_class-1.1 {
        Get priority class of another process
    } -constraints {
        nt
    } -body {
        twapi::get_priority_class [get_explorer_pid]
    } -result 32

    ################################################################

    test get_thread_relative_priority-1.0 {
        Get our thread priority
    } -constraints {
        nt
    } -body {
        twapi::get_thread_relative_priority [twapi::get_current_thread_id]
    } -result 0
    
    ###

    test get_thread_relative_priority-1.1 {
        Get thread priority of thread in another process
    } -constraints {
        nt
    } -body {
        twapi::get_thread_relative_priority [get_explorer_tid]
    } -result 0
    

    ################################################################

    test set_priority_class-1.0 {
        Set priority class of a process
    } -constraints {
        nt
    } -body {
        twapi::set_priority_class [pid] 0x8000
        set prio [twapi::get_priority_class [pid]]
        twapi::set_priority_class [pid] 0x20; # Reset priority
        set prio
    } -result 32768


    ################################################################

    test set_thread_relative_priority-1.0 {
        Set our thread priority
    } -constraints {
        nt
    } -body {
        twapi::set_thread_relative_priority [twapi::get_current_thread_id] 1
        set prio [twapi::get_thread_relative_priority [twapi::get_current_thread_id]]
        # Reset to normal
        twapi::set_thread_relative_priority [twapi::get_current_thread_id] 0
        set prio
    } -result 1

    ################################################################

    test wow64_process-1.0 {
        Test if current process is a WoW64 process
    } -constraints {
        nt
    } -body {
        twapi::wow64_process
    } -result [expr {$::tcl_platform(machine) eq "intel" && [info exists ::env(ProgramW6432)]}]

    ###
    test wow64_process-2.0 {
        Test a non-WoW64 process is correctly identified using -pid
    } -constraints {
        nt
    } -setup {
        set notepad_pid [start_program [file join $::env(SYSTEMROOT) System32 notepad.exe]]
    } -body {
        twapi::wow64_process -pid $notepad_pid
    } -cleanup {
        twapi::end_process $notepad_pid
    } -result 0

    ###
    test wow64_process-2.1 {
        Test a WoW64 process is correctly identified using -pid
    } -constraints {
        nt x64
    } -setup {
        set notepad_pid [start_program [file join $::env(SYSTEMROOT) SysWow64 notepad.exe]]
    } -body {
        twapi::wow64_process -pid $notepad_pid
    } -cleanup {
        twapi::end_process $notepad_pid
    } -result 1

    ###
    test wow64_process-3.0 {
        Test a non-WoW64 process is correctly identified using -hprocess
    } -constraints {
        nt
    } -setup {
        set notepad_pid [start_program [file join $::env(SYSTEMROOT) System32 notepad.exe]]
        set notepad_hpid [twapi::get_process_handle $notepad_pid]
    } -body {
        twapi::wow64_process -hprocess $notepad_hpid
    } -cleanup {
        twapi::CloseHandle $notepad_hpid
        twapi::end_process $notepad_pid
    } -result 0

    ###
    test wow64_process-3.1 {
        Test a WoW64 process is correctly identified using -hprocess
    } -constraints {
        nt x64
    } -setup {
        set notepad_pid [start_program [file join $::env(SYSTEMROOT) SysWow64 notepad.exe]]
        set notepad_hpid [twapi::get_process_handle $notepad_pid]
    } -body {
        twapi::wow64_process -hprocess $notepad_hpid
    } -cleanup {
        twapi::CloseHandle $notepad_hpid
        twapi::end_process $notepad_pid
    } -result 1


    ################################################################

    test virtualized_process-1.0 {
        Test if current process is a virtualized process
    } -constraints {
        nt
    } -body {
        twapi::virtualized_process
    } -result 0

    ###
    test virtualized_process-2.0 {
        Test a non-virtualized process is correctly identified using -pid
    } -constraints {
        nt
    } -setup {
        # Notepad is never virtualized
        set notepad_pid [start_program [file join $::env(SYSTEMROOT) System32 notepad.exe]]
    } -body {
        twapi::virtualized_process -pid $notepad_pid
    } -cleanup {
        twapi::end_process $notepad_pid
    } -result 0

    ###
    test virtalized_process-2.1 {
        Test a virtualized process is correctly identified using -pid
    } -constraints {
        nt TBD
    } -body {
    } -result 1

    ###
    test virtualized_process-3.0 {
        Test a non-virtualized process is correctly identified using -hprocess
    } -constraints {
        nt
    } -setup {
        set notepad_pid [start_program [file join $::env(SYSTEMROOT) System32 notepad.exe]]
        set notepad_hpid [twapi::get_process_handle $notepad_pid]
    } -body {
        twapi::virtualized_process -hprocess $notepad_hpid
    } -cleanup {
        twapi::CloseHandle $notepad_hpid
        twapi::end_process $notepad_pid
    } -result 0

    ###
    test virtualized_process-3.1 {
        Test a virtualized process is correctly identified using -hprocess
    } -constraints {
        nt TBD
    } -body {
    } -result 1

    ################################################################

    test set_process_virtualization-1.0 {
        Set current process virtualization
    } -constraints {
        nt
    } -body {
        # This is really the only useful operation for this command
        twapi::set_process_virtualization 0
    } -result ""

    ################################################################

    test get_pid_from_handle-1.0 {
    } -constraints {
        nt
    } -setup {
        set notepad_pid [start_program notepad.exe]
        set notepad_hpid [twapi::get_process_handle $notepad_pid]
    } -body {
        expr {[twapi::get_pid_from_handle $notepad_hpid] == $notepad_pid}
    } -cleanup {
        twapi::CloseHandle $notepad_hpid
        twapi::end_process $notepad_pid
    } -result 1


    ################################################################

    test get_process_elevation-1.0 {
        Get current process elevation
    } -constraints {
        nt
    } -body {
        twapi::get_process_elevation
    } -match oneof -result {default full limited}


    test get_process_elevation-2.0 {
        Get process elevation for another process
    } -constraints {
        nt
    } -setup {
        set np_pid [notepad_exec]
    } -body {
        twapi::get_process_elevation -pid $np_pid
    } -cleanup {
        twapi::end_process $np_pid -wait 500
    } -match oneof -result {default full limited}


    test get_process_elevation-3.0 {
        Get process elevation for another process using its process handle
    } -constraints {
        nt
    } -setup {
        set np_pid [start_program [file join $::env(SYSTEMROOT) System32 notepad.exe]]
        set np_hpid [twapi::get_process_handle $np_pid]
    } -body {
        twapi::get_process_elevation -hprocess $np_hpid
    } -cleanup {
        twapi::CloseHandle $np_hpid
        twapi::end_process $np_pid
    } -match oneof -result {default full limited}


    ################################################################

    test get_process_integrity-1.0 {
        Get current process integrity (numeric)
    } -constraints {
        nt
    } -body {
        twapi::get_process_integrity
    } -match inrange -result {0 65535}

    test get_process_integrity-1.1 {
        Get current process integrity (raw)
    } -constraints {
        nt
    } -body {
        twapi::get_process_integrity -raw
    } -match regexp -result {S-1-16-[[:digit:]]{1,5}}

    test get_process_integrity-1.2 {
        Get current process integrity (label)
    } -constraints {
        nt
    } -body {
        twapi::get_process_integrity -label
    } -match oneof -result {untrusted low medium high system}


    test get_process_integrity-2.0 {
        Get another process integrity using -pid
    } -setup {
        set np_pid [notepad_exec]
    } -body {
        twapi::get_process_integrity -pid $np_pid
    } -cleanup {
        twapi::end_process $np_pid
    } -match inrange -result {0 65535}

    test get_process_integrity-2.1 {
        Get another process integrity using -pid (raw)
    } -setup {
        set np_pid [notepad_exec]
    } -body {
        twapi::get_process_integrity -pid $np_pid -raw
    } -cleanup {
        twapi::end_process $np_pid
    } -match regexp -result {S-1-16-[[:digit:]]{1,5}}

    test get_process_integrity-2.2 {
        Get another process integrity using -pid (label)
    } -setup {
        set np_pid [notepad_exec]
    } -body {
        twapi::get_process_integrity -pid $np_pid -label
    } -cleanup {
        twapi::end_process $np_pid
    } -match oneof -result {untrusted low medium high system}


    test get_process_integrity-3.0 {
        Get another process integrity using -hprocess
    } -setup {
        set np_pid [notepad_exec]
        set np_hpid [twapi::get_process_handle $np_pid]
    } -body {
        twapi::get_process_integrity -hprocess $np_hpid
    } -cleanup {
        twapi::CloseHandle $np_hpid
        twapi::end_process $np_pid
    } -match inrange -result {0 65535}

    test get_process_integrity-3.1 {
        Get another process integrity using -hprocess (raw)
    } -setup {
        set np_pid [notepad_exec]
        set np_hpid [twapi::get_process_handle $np_pid]
    } -body {
        twapi::get_process_integrity -hprocess $np_hpid -raw
    } -cleanup {
        twapi::CloseHandle $np_hpid
        twapi::end_process $np_pid
    } -match regexp -result {S-1-16-[[:digit:]]{1,5}}

    test get_process_integrity-3.2 {
        Get another process integrity using -hprocess (label)
    } -setup {
        set np_pid [notepad_exec]
        set np_hpid [twapi::get_process_handle $np_pid]
    } -body {
        twapi::get_process_integrity -hprocess $np_hpid -label
    } -cleanup {
        twapi::CloseHandle $np_hpid
        twapi::end_process $np_pid
    } -match oneof -result {untrusted low medium high system}

    ################################################################

    test set_process_integrity-1.0 {
        Set current process integrity (numeric)
    } -constraints {
        nt
    } -setup {
        set integrity [twapi::get_process_integrity]
    } -body {
        # Don't want to change our own integrity so just checks 
        # for error conditions
        twapi::set_process_integrity $integrity
        expr {[twapi::get_process_integrity] == $integrity}
    } -result 1

    test set_process_integrity-1.1 {
        Set current process integrity (raw)
    } -constraints {
        nt
    } -setup {
        set integrity [twapi::get_process_integrity -raw]
    } -body {
        twapi::set_process_integrity $integrity
        expr {[twapi::get_process_integrity -raw] == $integrity}
    } -result 1

    test set_process_integrity-1.2 {
        Set current process integrity (label)
    } -constraints {
        nt
    } -setup {
        set integrity [twapi::get_process_integrity -label]
    } -body {
        twapi::set_process_integrity $integrity
        expr {[twapi::get_process_integrity -label] == $integrity}
    } -result 1

    test set_process_integrity-2.0 {
        Set another process integrity using -pid
    } -setup {
        set np_pid [notepad_exec]
    } -body {
        twapi::set_process_integrity $target_integrity -pid $np_pid
        twapi::get_process_integrity -pid $np_pid
    } -cleanup {
        twapi::end_process $np_pid
    } -result $target_integrity

    test set_process_integrity-2.1 {
        Set another process integrity using -pid (raw)
    } -setup {
        set np_pid [notepad_exec]
    } -body {
        twapi::set_process_integrity [expr {$have_integrity ? "S-1-16-4096" : "S-1-16-8192"}] -pid $np_pid
        twapi::get_process_integrity -pid $np_pid
    } -cleanup {
        twapi::end_process $np_pid
    } -result $target_integrity

    test set_process_integrity-2.2 {
        Set another process integrity using -pid (label)
    } -setup {
        set np_pid [notepad_exec]
    } -body {
        twapi::set_process_integrity [expr {$have_integrity ? "low" : "medium"}] -pid $np_pid
        twapi::get_process_integrity -pid $np_pid
    } -cleanup {
        twapi::end_process $np_pid
    } -result $target_integrity

    test set_process_integrity-3.0 {
        Set another process integrity using -hprocess
    } -setup {
        set np_pid [notepad_exec]
        set np_hpid [twapi::get_process_handle $np_pid]
    } -body {
        twapi::set_process_integrity $target_integrity -hprocess $np_hpid
        twapi::get_process_integrity -pid $np_pid
    } -cleanup {
        twapi::CloseHandle $np_hpid
        twapi::end_process $np_pid
    } -result $target_integrity

    test set_process_integrity-3.1 {
        Set another process integrity using -hprocess (raw)
    } -setup {
        set np_pid [notepad_exec]
        set np_hpid [twapi::get_process_handle $np_pid]
    } -body {
        twapi::set_process_integrity [expr {$have_integrity ? "S-1-16-4096" : "S-1-16-8192"}] -hprocess $np_hpid
        twapi::get_process_integrity -pid $np_pid
    } -cleanup {
        twapi::CloseHandle $np_hpid
        twapi::end_process $np_pid
    } -result $target_integrity

    test set_process_integrity-3.2 {
        Set another process integrity using -hprocess (label)
    } -setup {
        set np_pid [notepad_exec]
        set np_hpid [twapi::get_process_handle $np_pid]
    } -body {
        twapi::set_process_integrity [expr {$have_integrity ? "low" : "medium"}] -hprocess $np_hpid
        twapi::get_process_integrity -pid $np_pid
    } -cleanup {
        twapi::CloseHandle $np_hpid
        twapi::end_process $np_pid
    } -result $target_integrity


    ################################################################

    test process_in_administrators-1.0 {
        process_in_administrators
    } -constraints {
        nt TBD
    } -body {
    } -result 1

    ################################################################

    test get_process_commandline-1.0 {
        Get the command line of a process
    } -setup {
        set np_pid [exec notepad foo.txt &]
    } -body {
        twapi::get_process_commandline $np_pid
    } -cleanup {
        twapi::end_process $np_pid
    } -result "$::env(WINDIR)\\system32\\notepad.exe foo.txt" -match path

    test get_process_commandline-2.0 {
        Get the command line of a non-existent process
    } -body {
        twapi::get_process_commandline 12400
    } -result "(no such process)"

    test get_process_commandline-2.1 {
        Get the command line of a non-existent process -noexist
    } -body {
        twapi::get_process_commandline 12400 -noexist "not found"
    } -result "not found"

    test get_process_commandline-3.0 {
        Get the command line of a process without access
    } -setup {
        set pid [lindex [twapi::get_process_ids -name csrss.exe] 0]
    } -body {
        twapi::get_process_commandline $pid
    } -result "(unknown)"

    test get_process_commandline-3.1 {
        Get the command line of a process without access (-noaccess)
    } -setup {
        set pid [lindex [twapi::get_process_ids -name csrss.exe] 0]
    } -body {
        twapi::get_process_commandline $pid -noaccess "not allowed"
    } -result "not allowed"

    ################################################################

    test get_process_parent-1.0 {
        Get the parent of a process
    } -setup {
        set np_pid [exec notepad foo.txt &]
    } -body {
        twapi::get_process_parent $np_pid
    } -cleanup {
        twapi::end_process $np_pid
    } -result [pid]

    test get_process_parent-2.0 {
        Get the parent of a non-existent process
    } -body {
        twapi::get_process_parent 12400
    } -result "(no such process)"

    test get_process_parent-2.1 {
        Get the parent of a non-existent process -noexist
    } -body {
        twapi::get_process_parent 12400 -noexist "not found"
    } -result "not found"

    test get_process_parent-3.0 {
        Get the parent of a process without access
    } -constraints {
        TBD
    } -setup {
        set pid [what process has no access?]
    } -body {
        twapi::get_process_parent $pid
    } -result "(unknown)"

    test get_process_parent-3.1 {
        Get the parent of a process without access (-noaccess)
    } -constraints {
        TBD
    } -setup {
        set pid [what process has no access?]
    } -body {
        twapi::get_process_parent $pid -noaccess "not allowed"
    } -result "not allowed"

    test get_process_parent-4.0 {
        Get the parent of the system process
    } -body {
        twapi::get_process_parent 4
    } -result ""

    test get_process_parent-4.1 {
        Get the parent of the system idle process
    } -body {
        twapi::get_process_parent 0
    } -result ""


    ################################################################

    test get_process_exit_code-1.0 {
        Get exit code of a process
    } -setup {
        set child_fd [open "| [info nameofexecutable]" w+]
        fconfigure $child_fd -buffering line
        set child_pid [pid $child_fd]
    } -body {
        set child_h [twapi::get_process_handle $child_pid -access {process_all_access synchronize}]
        puts $child_fd "exit"
        if {[set status [twapi::wait_on_handle $child_h 1000]] ne "signalled"} {
            error "Child process wait ended with status $status"
        }
        twapi::get_process_exit_code $child_h
    } -cleanup {
        close $child_fd
        twapi::close_handle $child_h
    } -result 0

    test get_process_exit_code-1.1 {
        Get exit code of a process (non-default)
    } -setup {
        set child_fd [open "| [info nameofexecutable]" w+]
        fconfigure $child_fd -buffering line
        set child_pid [pid $child_fd]
    } -body {
        set child_h [twapi::get_process_handle $child_pid -access {process_all_access synchronize}]
        puts $child_fd "exit 123"
        if {[set status [twapi::wait_on_handle $child_h 1000]] ne "signalled"} {
            error "Child process wait ended with status $status"
        }
        twapi::get_process_exit_code $child_h
    } -cleanup {
        catch {close $child_fd}; # Since child process exits with non-0
        twapi::close_handle $child_h
    } -result 123

    test get_process_exit_code-1.2 {
        Get exit code of a running process
    } -setup {
        set child_fd [open "| [info nameofexecutable]" w+]
        fconfigure $child_fd -buffering line
        set child_pid [pid $child_fd]
    } -body {
        set child_h [twapi::get_process_handle $child_pid -access {process_all_access synchronize}]
        twapi::get_process_exit_code $child_h
    } -cleanup {
        puts $child_fd "exit"
        if {[set status [twapi::wait_on_handle $child_h 1000]] ne "signalled"} {
            error "Child process wait ended with status $status"
        }
        close $child_fd
        twapi::close_handle $child_h
    } -result ""

    ################################################################

    test suspend_thread-1.0 {
        Suspend a thread
    } -setup {
        set pid [notepad_exec]
        set tids [lindex [twapi::get_process_info $pid -tids] 1]
    } -body {
        foreach tid $tids {
            twapi::suspend_thread $tid
        }
        twapi::end_process $pid -wait 100
        twapi::process_exists $pid;    # SHould still exist (since suspended, cannot process WM_CLOSE)
    } -cleanup {
        foreach tid $tids {
            twapi::resume_thread $tid
        }
        after 100
        catch { twapi::end_process $pid -force }
    } -result 1

    ################################################################

    test resume_thread-1.0 {
        Resume a thread
    } -setup {
        set pid [notepad_exec]
        set tids [lindex [twapi::get_process_info $pid -tids] 1]
    } -body {
        foreach tid $tids {
            twapi::suspend_thread $tid
        }
        if {[twapi::end_process $pid -wait 100]} {
            error "Could not suspend notepad."
        }
        # Now resume. It should exit since WM_QUIT will be picked up
        foreach tid $tids {
            twapi::resume_thread $tid
        }
        after 50
        twapi::process_exists $pid
    } -result 0

    ################################################################

    test get_module_handle-1.0 {
        Get the handle to a module with no options specified
    } -body {
        set h [twapi::get_module_handle]
        twapi::pointer_type $h
    } -cleanup {
        twapi::free_library $h
    } -result HMODULE

    test get_module_handle-2.0 {
        Get the handle to a module from a specific dll name
    } -body {
        set h [twapi::get_module_handle -path advapi32.dll]
        twapi::pointer_type $h
    } -cleanup {
        twapi::free_library $h
    } -result HMODULE

    test get_module_handle-2.1 {
        Get the handle to a module from a specific dll path
    } -body {
        set h [twapi::get_module_handle -path [file join $::env(WINDIR) system32 advapi32.dll]]
        twapi::pointer_type $h
    } -cleanup {
        twapi::free_library $h
    } -result HMODULE

    test get_module_handle-3.0 {
        Get the handle to a module with -pin false
    } -body {
        set h [twapi::get_module_handle -path [file join $::env(WINDIR) system32 advapi32.dll] -pin false]
        # No way to really check if module is pinned or not
        twapi::pointer_type $h
    } -cleanup {
        twapi::free_library $h
    } -result HMODULE

    test get_module_handle-3.1 {
        Get the handle to a module with -pin true
    } -body {
        set h [twapi::get_module_handle -path [file join $::env(WINDIR) system32 advapi32.dll] -pin 1]
        # No way to really check if module is pinned or not
        twapi::pointer_type $h
    } -cleanup {
        twapi::free_library $h
    } -result HMODULE


    ################################################################

    test get_module_handle_from_address-1.0 {
        Get the handle to a module based on its address
    } -body {
        set addr [twapi::pointer_to_address [lindex [twapi::get_process_modules [pid]] 1]]
        set h [twapi::get_module_handle_from_address $addr]
        list [twapi::pointer_type $h] [expr {[lindex $h 0] == $addr}]
    } -cleanup {
        twapi::free_library $h
    } -result {HMODULE 1}

    test get_module_handle_from_address-1.1 {
        Get the handle to a module based on its address
    } -body {
        set addr [twapi::pointer_to_address [lindex [twapi::get_process_modules [pid]] 1]]
        set h [twapi::get_module_handle_from_address [expr {$addr + 4}]]
        list [twapi::pointer_type $h] [expr {[lindex $h 0] == $addr}]
    } -cleanup {
        twapi::free_library $h
    } -result {HMODULE 1}

    test get_module_handle_from_address-2.0 {
        Get the handle to a module based on its address with -pin false
    } -body {
        set addr [twapi::pointer_to_address [lindex [twapi::get_process_modules [pid]] 1]]
        set h [twapi::get_module_handle_from_address $addr -pin 0]
        # No way to really check if module is pinned or not
        list [twapi::pointer_type $h] [expr {[lindex $h 0] == $addr}]
    } -cleanup {
        twapi::free_library $h
    } -result {HMODULE 1}

    test get_module_handle_from_address-2.1 {
        Get the handle to a module based on its address with -pin true
    } -body {
        set addr [twapi::pointer_to_address [lindex [twapi::get_process_modules [pid]] 1]]
        set h [twapi::get_module_handle_from_address [expr {$addr + 4}] -pin true] 
        # No way to really check if module is pinned or not
        list [twapi::pointer_type $h] [expr {[lindex $h 0] == $addr}]
    } -cleanup {
        twapi::free_library $h
    } -result {HMODULE 1}

    ################################################################

    test is_system_pid-1.0 {
        Check system pid (match)
    } -body {
        twapi::is_system_pid 4
    } -result 1

    test is_system_pid-1.1 {
        Check system pid (no)
    } -body {
        twapi::is_system_pid 0; # Idle process
    } -result 0

    test is_system_pid-1.2 {
        Check system pid (no)
    } -body {
        twapi::is_system_pid [pid]
    } -result 0

    ################################################################

    test get_system_environment_vars-1.0 {
        Get system environment variables
    } -body {
        unset -nocomplain vars
        set vars [twapi::get_system_environment_vars]
        list [dict get $vars COMPUTERNAME] [llength [dict keys $vars HOME*]]
    } -result [list $::env(COMPUTERNAME) 0]

    test get_system_environment_vars-2.0 {
        Get system environment variables -normalize 0
    } -body {
        unset -nocomplain vars
        set vars [twapi::get_system_environment_vars -normalize 0]
        list [dict exists $vars SYSTEMROOT] [dict exists $vars SystemRoot] [llength [dict keys $vars HOME*]]
    } -result [list 0 1 0]

    test get_system_environment_vars-2.1 {
        Get system environment variables -normalize 1
    } -body {
        unset -nocomplain vars
        set vars [twapi::get_system_environment_vars -normalize 1]
        list [dict get $vars SYSTEMROOT] [llength [dict keys $vars HOME*]]
    } -result [list $::env(systemroot) 0]

    ################################################################

    test get_user_environment_vars-1.0 {
        Get user environment variables
    } -setup {
        set tok [twapi::open_process_token -access {token_query token_duplicate token_impersonate}]
    } -body { 
        unset -nocomplain vars
        set vars [twapi::get_user_environment_vars $tok]
        dict get $vars HOMEDRIVE
    } -cleanup {
        twapi::close_token $tok
    } -result $::env(HOMEDRIVE)

    test get_user_environment_vars-2.0 {
        Get user environment variables -normalize 0
    } -setup {
        set tok [twapi::open_process_token -access {token_query token_duplicate token_impersonate}]
    } -body {
        unset -nocomplain vars
        set vars [twapi::get_user_environment_vars $tok -normalize 0]
        list [dict exists $vars SYSTEMROOT] [dict exists $vars SystemRoot] [dict exists $vars HOMEDRIVE]
    } -cleanup {
        twapi::close_token $tok
    } -result [list 0 1 1]

    test get_user_environment_vars-2.1 {
        Get user environment variables -normalize 1
    } -setup {
        set tok [twapi::open_process_token -access {token_query token_duplicate token_impersonate}]
    } -body {
        unset -nocomplain vars
        set vars [twapi::get_user_environment_vars $tok -normalize 1]
        list [dict get $vars SYSTEMROOT] [dict get $vars HOMEDRIVE]
    } -cleanup {
        twapi::close_token $tok
    } -result [list $::env(systemroot) $::env(homedrive)]

    test get_user_environment_vars-3.0 {
        Get user environment variables -inherit
    } -setup {
        set tok [twapi::open_process_token -access {token_query token_duplicate token_impersonate}]
    } -body {
        unset -nocomplain vars
        unset -nocomplain vars2
        set ::env(TWAPIENVTEST) "XXX"
        set vars [twapi::get_user_environment_vars $tok -inherit 0]
        set vars2 [twapi::get_user_environment_vars $tok -inherit 1]
        list [dict exists $vars TWAPIENVTEST] [dict get $vars2 TWAPIENVTEST]
    } -cleanup {
        unset ::env(TWAPIENVTEST)
        twapi::close_token $tok
    } -result [list 0 XXX]

    ################################################################

    test expand_environment_vars-1.0 {
        Expand a single environment string
    } -body {
        twapi::expand_environment_vars "%USERNAME%"
    } -result $::env(USERNAME)

    test expand_environment_vars-1.1 {
        Expand a single embedded environment string
    } -body {
        twapi::expand_environment_vars "x%USERNAME%y"
    } -result x$::env(USERNAME)y

    test expand_environment_vars-1.2 {
        Expand multiple embedded environment strings
    } -body {
        twapi::expand_environment_vars "x%USERNAME%y%computername%"
    } -result x$::env(USERNAME)y$::env(COMPUTERNAME)

    test expand_environment_vars-1.3 {
        Expand long embedded environment strings
    } -body {
        twapi::expand_environment_vars [string repeat %PATH% 10]
    } -result [string repeat $::env(PATH) 10]

    test expand_environment_vars-2.0 {
        Verify environment variables that are not defined are not modified
    } -body {
        twapi::expand_environment_vars "x%USERNAME%y%XXX%z"
    } -result x$::env(USERNAME)y%XXX%z

    test expand_environment_vars-2.1 {
        Expand empty string
    } -body {
        twapi::expand_environment_vars ""
    } -result ""

    test expand_environment_vars-2.2 {
        Expand a string matching env name but without %
    } -body {
        twapi::expand_environment_vars "USERNAME"
    } -result "USERNAME"

    ################################################################

    test expand_system_environment_vars-1.0 {
        Expand a single environment string
    } -body {
        twapi::expand_system_environment_vars "%USERNAME%"
    } -result SYSTEM

    test expand_system_environment_vars-1.1 {
        Expand a single embedded environment string
    } -body {
        twapi::expand_system_environment_vars "x%USERNAME%y"
    } -result xSYSTEMy

    test expand_system_environment_vars-1.2 {
        Expand multiple embedded environment strings
    } -body {
        twapi::expand_system_environment_vars "x%USERNAME%y%computername%"
    } -result xSYSTEMy$::env(COMPUTERNAME)

    test expand_system_environment_vars-1.3 {
        Expand long embedded environment strings
    } -body {
        set val [dict get [twapi::get_system_environment_vars -normalize 1] PATH]
        string equal [twapi::expand_system_environment_vars [string repeat %PATH% 50]] [string repeat $val 50]
    } -result 1

    test expand_system_environment_vars-2.0 {
        Verify environment variables that are not defined are not modified
    } -body {
        twapi::expand_system_environment_vars "x%USERNAME%y%XXX%z"
    } -result xSYSTEMy%XXX%z

    test expand_system_environment_vars-2.1 {
        Expand empty string
    } -body {
        twapi::expand_system_environment_vars ""
    } -result ""

    test expand_system_environment_vars-2.2 {
        Expand a string matching env name but without %
    } -body {
        twapi::expand_system_environment_vars "USERNAME"
    } -result "USERNAME"

    ################################################################

    test expand_user_environment_vars-1.0 {
        Expand a single environment string
    } -setup {
        set tok [twapi::open_process_token -access {token_query token_duplicate token_impersonate}]
    } -body {
        twapi::expand_user_environment_vars $tok "%USERNAME%"
    } -cleanup {
        twapi::close_token $tok
    } -result $::env(USERNAME)

    test expand_user_environment_vars-1.1 {
        Expand a single embedded environment string
    } -setup {
        set tok [twapi::open_process_token -access {token_query token_duplicate token_impersonate}]
    } -body {
        twapi::expand_user_environment_vars $tok "x%TEMP%y"
    } -cleanup {
        twapi::close_token $tok
    } -result x$::env(TEMP)y

    test expand_user_environment_vars-1.2 {
        Expand multiple embedded environment strings
    } -setup {
        set tok [twapi::open_process_token -access {token_query token_duplicate token_impersonate}]
    } -cleanup {
        twapi::close_token $tok
    } -body {
        twapi::expand_user_environment_vars $tok "x%username%y%computername%"
    } -result x$::env(USERNAME)y$::env(COMPUTERNAME)

    test expand_user_environment_vars-1.3 {
        Expand long embedded environment strings
    } -setup {
        set tok [twapi::open_process_token -access {token_query token_duplicate token_impersonate}]
    } -cleanup {
        twapi::close_token $tok
    } -body {
        set val [dict get [twapi::get_user_environment_vars $tok -normalize 1] PATH]
        string equal [twapi::expand_user_environment_vars $tok [string repeat %PATH% 50]] [string repeat $val 50]
    } -result 1

    test expand_user_environment_vars-1.4 {
        Verify current process environment not used
    } -setup {
        set tok [twapi::open_process_token -access {token_query token_duplicate token_impersonate}]
        set saved_temp $::env(TEMP)
        set ::env(TEMP) c:\\nosuchtemp
    } -cleanup {
        set ::env(TEMP) $saved_temp
        twapi::close_token $tok
    } -body {
        string equal [twapi::expand_user_environment_vars $tok "%TEMP%"] $saved_temp
    } -result 1

    test expand_user_environment_vars-2.0 {
        Verify environment variables that are not defined are not modified
    } -setup {
        set tok [twapi::open_process_token -access {token_query token_duplicate token_impersonate}]
    } -cleanup {
        twapi::close_token $tok
    } -body {
        twapi::expand_user_environment_vars $tok "x%USERNAME%y%XXX%z"
    } -result x$::env(USERNAME)y%XXX%z

    test expand_user_environment_vars-2.0 {
        Expand empty string
    } -setup {
        set tok [twapi::open_process_token -access {token_query token_duplicate token_impersonate}]
    } -cleanup {
        twapi::close_token $tok
    } -body {
        twapi::expand_user_environment_vars $tok ""
    } -result ""

    test expand_user_environment_vars-2.1 {
        Expand a string matching env name but without %
    } -setup {
        set tok [twapi::open_process_token -access {token_query token_duplicate token_impersonate}]
    } -cleanup {
        twapi::close_token $tok
    } -body {
        twapi::expand_user_environment_vars $tok "USERNAME"
    } -result "USERNAME"

    ################################################################

    test load_user_profile-1.0 {
        Load user profile
    } -setup {
        set tok [twapi::open_process_token -access {token_duplicate token_impersonate token_query}]
    } -body {
        twapi::unload_user_profile $tok [twapi::load_user_profile $tok]
    } -cleanup {
        twapi::close_token $tok
    } -result ""

    test load_user_profile-99.0 {
        Validate other options
    } -constraints {
        TBD
    } -body {
        TBD
    }

    test unload_user_profile-1.0 {
    } -constraints {
        TBD
    } -body {
        TBD
    }

    ################################################################
    proc memory_info_check {pid meminfo} {
        array set procinfo $meminfo
        set result ""
        foreach field {
            -workingsetpeak -workingset
            -poolpagedbytes -poolnonpagedbytes
            -pagefilebytes -pagefilebytespeak
        } {
            set checkval [get_process_field $pid $field]
            if {![approx $checkval $procinfo($field)]} {
                append result "Mismatch in $field. $checkval !~ $procinfo($field).\n"
            }
        }
        if {![string is entier $procinfo(-pagefaults)]} {
            append result "Invalid -pagefault field.\n"
        }
        if {$procinfo(-poolpagedbytespeak) < $procinfo(-poolpagedbytes)} {
            append result "-poolpagedbytespeak < -poolpagedbytes. ($procinfo(-poolpagedbytespeak) < $procinfo(-poolpagedbytes)).\n"
        }
        if {$procinfo(-poolnonpagedbytespeak) < $procinfo(-poolnonpagedbytes)} {
            append result "-poolnonpagedbytespeak < -poolnonpagedbytes. ($procinfo(-poolnonpagedbytespeak) < $procinfo(-poolnonpagedbytes)).\n"
        }
        if {$procinfo(-privatebytes) > $procinfo(-pagefilebytespeak)} {
            append result "-privatebytes > -pagefilebytespeak. ($procinfo(-privatebytes) > $procinfo(-pagefilebytespeak)).\n"
        }
        return $result
    }
    test get_process_memory_info-1.0 {
        get_process_memory_info - no args
    } -constraints {
        nt
    } -body {
        memory_info_check [pid] [twapi::get_process_memory_info]
    } -result ""

    test get_process_memory_info-1.1 {
        get_process_memory_info current process
    } -constraints {
        nt
    } -body {
        memory_info_check [pid] [twapi::get_process_memory_info [pid]]
    } -result ""

    test get_process_memory_info-1.2 {
        get_process_memory_info another process
    } -constraints {
        nt
    } -body {
        set pid [get_explorer_pid]
        memory_info_check $pid [twapi::get_process_memory_info $pid]
    } -result ""
    ################################################################

}
::tcltest::cleanupTests
namespace delete ::twapi::process::test
