210 lines
5.7 KiB
Go
210 lines
5.7 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 veriftool
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"github.com/sergi/go-diff/diffmatchpatch"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
u "tools/srcs/common"
|
|
"tools/srcs/dependtool"
|
|
)
|
|
|
|
func RunVerificationTool(homeDir string) {
|
|
|
|
// Init and parse local arguments
|
|
args := new(u.Arguments)
|
|
p, err := args.InitArguments("--verif",
|
|
"The Output verifier tool allows to compare the output of a program ported as unikernel")
|
|
if err != nil {
|
|
u.PrintErr(err)
|
|
}
|
|
if err := parseLocalArguments(p, args); err != nil {
|
|
u.PrintErr(err)
|
|
}
|
|
|
|
// Get program path
|
|
programPath, err := u.GetProgramPath(&*args.StringArg[programArg])
|
|
if err != nil {
|
|
u.PrintErr("Could not determine program path", err)
|
|
}
|
|
|
|
// Get program Name
|
|
programName := *args.StringArg[programArg]
|
|
|
|
// Take base path if absolute path is used
|
|
if filepath.IsAbs(programName) {
|
|
programName = filepath.Base(programName)
|
|
}
|
|
|
|
var workspacePath = homeDir + u.SEP + u.WORKSPACEFOLDER
|
|
if len(*args.StringArg[workspaceArg]) > 0 {
|
|
workspacePath = *args.StringArg[workspaceArg]
|
|
}
|
|
|
|
// Get the app folder
|
|
var appFolder string
|
|
if workspacePath[len(workspacePath)-1] != os.PathSeparator {
|
|
appFolder = workspacePath + u.SEP + u.APPSFOLDER + programName + u.SEP
|
|
} else {
|
|
appFolder = workspacePath + u.APPSFOLDER + programName + u.SEP
|
|
}
|
|
|
|
// Get the build folder
|
|
buildAppFolder := appFolder + u.BUILDFOLDER
|
|
|
|
// Get KVM image
|
|
var kvmUnikernelPath, kvmUnikernel string
|
|
if file, err := u.OSReadDir(buildAppFolder); err != nil {
|
|
u.PrintWarning(err)
|
|
} else {
|
|
for _, f := range file {
|
|
if !f.IsDir() && strings.Contains(f.Name(), u.KVM_IMAGE) &&
|
|
len(filepath.Ext(f.Name())) == 0 {
|
|
kvmUnikernel = f.Name()
|
|
kvmUnikernelPath = filepath.Join(buildAppFolder, f.Name())
|
|
}
|
|
}
|
|
}
|
|
|
|
// Kvm unikernel image
|
|
if len(kvmUnikernel) == 0 {
|
|
u.PrintWarning(errors.New("no KVM image found"))
|
|
}
|
|
|
|
// Filepath of output
|
|
unikernelFilename := appFolder + "output_" + kvmUnikernel + ".txt"
|
|
appFilename := appFolder + "output_" + programName + ".txt"
|
|
|
|
// Read test
|
|
argStdin := ""
|
|
if len(*args.StringArg[testFileArg]) > 0 {
|
|
testingStruct := &dependtool.Testing{}
|
|
if len(*args.StringArg[testFileArg]) > 0 {
|
|
var err error
|
|
testingStruct, err = dependtool.ReadTestFileJson(*args.StringArg[testFileArg])
|
|
if err != nil {
|
|
u.PrintWarning("Cannot find test file: " + err.Error())
|
|
}
|
|
}
|
|
option := ""
|
|
if len(*args.StringArg[optionsArg]) > 0 {
|
|
option = *args.StringArg[optionsArg]
|
|
}
|
|
|
|
str_b := ""
|
|
for i := 0; i < 45; i++ {
|
|
str_b += "\n"
|
|
}
|
|
|
|
outStr, _ := dependtool.RunVerifCommandTester(programPath, programName, option, testingStruct)
|
|
if err := u.WriteToFile(appFilename, []byte(str_b+outStr)); err != nil {
|
|
u.PrintWarning("Impossible to write the output of verification to " +
|
|
appFilename)
|
|
} else {
|
|
u.PrintInfo("Output of general application written to " + appFilename)
|
|
}
|
|
|
|
option = "-nographic -vga none -device isa-debug-exit -kernel " + kvmUnikernelPath
|
|
|
|
outStr, _ = dependtool.RunVerifCommandTester("qemu-system-x86_64", "qemu-system-x86_64", option, testingStruct)
|
|
if err := u.WriteToFile(unikernelFilename, []byte(str_b+outStr)); err != nil {
|
|
u.PrintWarning("Impossible to write the output of verification to " +
|
|
kvmUnikernel)
|
|
} else {
|
|
u.PrintInfo("Output of unikernel written to " + unikernelFilename)
|
|
}
|
|
|
|
} else {
|
|
// No test file
|
|
if err := testUnikernel(buildAppFolder+kvmUnikernel, unikernelFilename,
|
|
[]byte(argStdin)); err != nil {
|
|
u.PrintWarning("Impossible to write the output of verification to " +
|
|
unikernelFilename)
|
|
}
|
|
|
|
// Test general app
|
|
if err := testApp(programName, appFilename, []byte(argStdin)); err != nil {
|
|
u.PrintWarning("Impossible to write the output of verification to " +
|
|
appFilename)
|
|
} else {
|
|
u.PrintInfo("Output of general application writtent to " + appFilename)
|
|
}
|
|
}
|
|
|
|
c := askForConfirmation("Do you want to see a diff between the two output")
|
|
if c {
|
|
u.PrintInfo("Comparison output:")
|
|
|
|
// Compare both output
|
|
fmt.Println(compareOutput(unikernelFilename, appFilename))
|
|
}
|
|
}
|
|
|
|
func compareOutput(unikernelFilename, appFilename string) string {
|
|
f1, err := ioutil.ReadFile(unikernelFilename)
|
|
if err != nil {
|
|
u.PrintErr(err)
|
|
}
|
|
|
|
f2, err := ioutil.ReadFile(appFilename)
|
|
if err != nil {
|
|
u.PrintErr(err)
|
|
}
|
|
|
|
dmp := diffmatchpatch.New()
|
|
|
|
diffs := dmp.DiffMain(string(f2), string(f1), false)
|
|
|
|
return dmp.DiffPrettyText(diffs)
|
|
}
|
|
|
|
func testApp(programName, outputFile string, argsStdin []byte) error {
|
|
bOut, _ := u.ExecuteRunCmdStdin(programName, argsStdin)
|
|
|
|
return u.WriteToFile(outputFile, bOut)
|
|
}
|
|
|
|
func testUnikernel(kvmUnikernel, outputFile string, argsStdin []byte) error {
|
|
argsQemu := []string{"-nographic", "-vga", "none", "-device",
|
|
"isa-debug-exit", "-kernel", kvmUnikernel}
|
|
|
|
bOut, _ := u.ExecuteRunCmdStdin("qemu-system-x86_64", argsStdin, argsQemu...)
|
|
|
|
return u.WriteToFile(outputFile, bOut)
|
|
}
|
|
|
|
// askForConfirmation asks the user for confirmation. A user must type in "yes" or "no" and
|
|
// then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as
|
|
// confirmations. If the input is not recognized, it will ask again. The function does not return
|
|
// until it gets a valid response from the user.
|
|
func askForConfirmation(s string) bool {
|
|
reader := bufio.NewReader(os.Stdin)
|
|
|
|
for {
|
|
fmt.Printf("%s [y/n]: ", s)
|
|
|
|
response, err := reader.ReadString('\n')
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
|
|
response = strings.ToLower(strings.TrimSpace(response))
|
|
|
|
if response == "y" || response == "yes" {
|
|
return true
|
|
} else if response == "n" || response == "no" {
|
|
return false
|
|
}
|
|
}
|
|
}
|