gaulthiergain-tools/srcs/dependtool/tester.go
Gaulthier Gain 4039686867 Main update
2022-09-09 11:43:03 +02:00

337 lines
9.2 KiB
Go

// Copyright 2019 The UNICORE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file
//
// Author: Gaulthier Gain <gaulthier.gain@uliege.be>
package dependtool
import (
"bytes"
"context"
"fmt"
"math/rand"
"net"
"os/exec"
"strconv"
"strings"
"syscall"
"time"
u "tools/srcs/common"
)
const (
stdinTest = iota
execTest
telnetTest
externalTesting
)
const (
stdinTestString = "stdin"
execTestString = "exec"
telnetTestString = "telnet"
)
const (
timeOutMs = 3000
startupSec = 3
)
type Testing struct {
TypeTest string `json:"typeTest"`
TimeOutTest int `json:"timeOutMsTest"`
// Only for telnet test
AddressTelnet string `json:"addressTelnet"`
PortTelnet int `json:"portTelnet"`
TimeCommand int32 `json:"timeMsCommand"`
ListCommands []string `json:"listCommands"`
}
// checkTypeTest checks the type of testing: exec, stdin and telnet tests.
//
// It returns an integer which represents the type of tests
func checkTypeTest(testStruct *Testing) int {
if testStruct == nil {
return externalTesting
}
if strings.Compare(testStruct.TypeTest, stdinTestString) == 0 {
return stdinTest
} else if strings.Compare(testStruct.TypeTest, execTestString) == 0 {
return execTest
} else if strings.Compare(testStruct.TypeTest, telnetTestString) == 0 {
return telnetTest
}
return externalTesting
}
// setDurationTimeOut sets the duration of the timeout by computing a reference
// value. In addition an extra margin value is added (3sec).
//
// It returns a duration either in milliseconds or in seconds.
func setDurationTimeOut(t *Testing, waitTime int) time.Duration {
if checkTypeTest(t) != externalTesting {
// Compute the number of commands + execution time (+ 3 seconds safe margin)
totalMs := t.TimeCommand*int32(len(t.ListCommands)) + timeOutMs
return time.Duration(totalMs) * time.Millisecond
}
return time.Duration(waitTime+startupSec) * time.Second
}
// runCommandTester run commands and captures stdout and stderr of a the
// executed command. It will also run the Tester to explore several execution
// paths of the given app.
//
// It returns to string which are respectively stdout and stderr.
func runCommandTester(programPath, programName, command, option string,
testStruct *Testing, dArgs DynamicArgs, data *u.DynamicData) (string, string) {
timeOut := setDurationTimeOut(testStruct, dArgs.waitTime)
u.PrintInfo("Max testing duration of " + programName + " : " + timeOut.String())
ctx, cancel := context.WithTimeout(context.Background(), timeOut)
defer cancel()
args := strings.Fields("-f " + programPath + " " + option)
cmd := exec.CommandContext(ctx, command, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
bufOut, bufErr, bufIn := &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}
cmd.Stdout = bufOut // Add io.MultiWriter(os.Stdout) to record on stdout
cmd.Stderr = bufErr // Add io.MultiWriter(os.Stderr) to record on stderr
if checkTypeTest(testStruct) == stdinTest {
cmd.Stdin = bufIn
for _, cmds := range testStruct.ListCommands {
time.Sleep(100 * time.Millisecond)
bufIn.Write([]byte(cmds + "\n"))
}
}
// Run the process (traced by strace/ltrace)
if err := cmd.Start(); err != nil {
u.PrintErr(err)
}
// Run a go routine to handle the tests
go func() {
if checkTypeTest(testStruct) != stdinTest {
Tester(programName, cmd, data, testStruct, dArgs)
// Kill the program after the tester has finished the job
if err := u.PKill(programName, syscall.SIGINT); err != nil {
u.PrintErr(err)
}
}
}()
// Ignore the error because the program is killed (waitTime)
_ = cmd.Wait()
if ctx.Err() == context.DeadlineExceeded {
u.PrintInfo("Time out during executing: " + cmd.String())
return bufOut.String(), bufErr.String()
}
return bufOut.String(), bufErr.String()
}
func RunVerifCommandTester(programPath, programName, option string, testStruct *Testing) (string, string) {
timeOut := setDurationTimeOut(testStruct, 1)
u.PrintInfo("Max testing duration of " + programName + " : " + timeOut.String())
ctx, cancel := context.WithTimeout(context.Background(), timeOut)
defer cancel()
args := strings.Fields(option)
cmd := exec.CommandContext(ctx, programPath, args...)
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
bufOut, bufErr, bufIn := &bytes.Buffer{}, &bytes.Buffer{}, &bytes.Buffer{}
cmd.Stdout = bufOut // Add io.MultiWriter(os.Stdout) to record on stdout
cmd.Stderr = bufErr // Add io.MultiWriter(os.Stderr) to record on stderr
if checkTypeTest(testStruct) == stdinTest {
cmd.Stdin = bufIn
bufIn.Write([]byte(" \n"))
for _, cmds := range testStruct.ListCommands {
if strings.Contains(programName, "qemu-system-x86_64") {
time.Sleep(1000 * time.Millisecond)
} else {
time.Sleep(100 * time.Millisecond)
}
bufIn.Write([]byte(cmds + "\n"))
}
}
// Run the process
if err := cmd.Start(); err != nil {
u.PrintErr(err)
}
// Run a go routine to handle the tests
go func() {
if checkTypeTest(testStruct) != stdinTest {
VerifTester(testStruct)
// Kill the program after the tester has finished the job
if err := u.PKill(programName, syscall.SIGINT); err != nil {
u.PrintErr(err)
}
}
}()
// Ignore the error because the program is killed (waitTime)
_ = cmd.Wait()
if ctx.Err() == context.DeadlineExceeded {
u.PrintInfo("Time out during executing: " + cmd.String())
return bufOut.String(), bufErr.String()
}
return bufOut.String(), bufErr.String()
}
// Tester runs the executable file of a given application to perform tests to
// get program dependencies.
//
func Tester(programName string, cmd *exec.Cmd, data *u.DynamicData,
testStruct *Testing, dArgs DynamicArgs) {
if len(dArgs.testFile) > 0 {
// Wait until the program has started
time.Sleep(time.Second * startupSec)
u.PrintInfo("Run internal tests from file " + dArgs.testFile)
// Launch execution tests
if checkTypeTest(testStruct) == execTest {
launchTestsExternal(testStruct)
} else if checkTypeTest(testStruct) == telnetTest {
if len(testStruct.AddressTelnet) == 0 || testStruct.PortTelnet == 0 {
u.PrintWarning("Cannot find Address and port for telnet " +
"within json file. Skip tests")
} else {
launchTelnetTest(testStruct)
}
}
} else {
u.PrintInfo("Waiting for external tests for " + strconv.Itoa(
dArgs.waitTime) + " sec")
ticker := time.Tick(time.Second)
for i := 1; i <= dArgs.waitTime; i++ {
<-ticker
fmt.Printf("-")
}
fmt.Printf("\n")
}
// Gather shared libs
u.PrintHeader2("(*) Gathering shared libs")
if err := gatherDynamicSharedLibs(programName, cmd.Process.Pid, data,
dArgs.fullDeps); err != nil {
u.PrintWarning(err)
}
}
func VerifTester(testStruct *Testing) {
// Wait until the program has started
time.Sleep(time.Second * startupSec)
u.PrintInfo("Run internal tests from test file")
// Launch execution tests
if checkTypeTest(testStruct) == execTest {
launchTestsExternal(testStruct)
} else if checkTypeTest(testStruct) == telnetTest {
if len(testStruct.AddressTelnet) == 0 || testStruct.PortTelnet == 0 {
u.PrintWarning("Cannot find Address and port for telnet " +
"within json file. Skip tests")
} else {
launchTelnetTest(testStruct)
}
}
}
//----------------------------------Tests---------------------------------------
// launchTestsExternal runs external tests written in the 'test.json' file.
//
func launchTestsExternal(testStruct *Testing) {
for _, cmd := range testStruct.ListCommands {
if len(cmd) > 0 {
// Perform a sleep between command if specified
if testStruct.TimeCommand > 0 {
timeMs := rand.Int31n(testStruct.TimeCommand)
time.Sleep(time.Duration(timeMs) * time.Millisecond)
}
// Execute each line as a command
if _, err := u.ExecutePipeCommand(cmd); err != nil {
u.PrintWarning("Impossible to execute test: " + cmd)
} else {
u.PrintInfo("Test executed: " + cmd)
}
}
}
}
// launchTelnetTest runs telnet tests written in the 'test.json' file.
//
func launchTelnetTest(testStruct *Testing) {
addr := testStruct.AddressTelnet + ":" + strconv.Itoa(testStruct.PortTelnet)
conn, _ := net.Dial("tcp", addr)
for _, cmd := range testStruct.ListCommands {
if len(cmd) > 0 {
// Perform a sleep between command if specified
if testStruct.TimeCommand > 0 {
timeMs := rand.Int31n(testStruct.TimeCommand)
time.Sleep(time.Duration(timeMs) * time.Millisecond)
}
// Set a timeout to avoid blocking
if err := conn.SetReadDeadline(
time.Now().Add(time.Duration(timeOutMs) * time.Millisecond)); err != nil {
u.PrintWarning("Impossible to set a timeout to TCP command")
}
// Send commands (test)
if _, err := fmt.Fprintf(conn, cmd+"\n"); err != nil {
u.PrintWarning("Impossible to execute test: " + cmd)
} else {
u.PrintInfo("Test executed: " + cmd)
}
// Read response
message := readerTelnet(conn)
fmt.Println("----->Message from server: " + message)
}
}
}
// readerTelnet reads data from the telnet connection.
//
func readerTelnet(conn net.Conn) (out string) {
var buffer [1]byte
recvData := buffer[:]
var n int
var err error
for {
n, err = conn.Read(recvData)
if n <= 0 || err != nil {
break
} else {
out += string(recvData)
}
}
return out
}