5.17. How to Interact with Files and with the Environment in Test Scripts

Table of Contents

5.17.1. How to Interact with External Files in Test Scripts
5.17.2. How to Compare External Files inside Test Scripts
5.17.3. How to Read Environment Variables inside Test Scripts

In addition to the test-specific functionality that Squish provides, test scripts can also use the native functionality (including the standard libraries) provided by the scripting languages themselves. In this subsection we will show how to use native functionality to read data from an external file, write data to an external file, check for the existence of an external file, and delete an external file. In addition, we will see how to compare two external files, and also how to read the environment variables that are set when the script is running.

[Note]Python-specific

The Python examples don't show any import statements, but these are of course required when non-global functions are used. The imports ought to be done before the main function is defined, with those shown below being sufficient for the examples shown in this section.

Python
import codecs, filecmp, os, subprocess, sys
[Note]Perl-specific

The Perl examples don't show any use statements, but these are of course required when non-global functions are used. The uses ought to be done before the main function is defined, with those shown below being sufficient for the examples shown in this section.

Perl
use File::Basename;
use File::Spec;

5.17.1. How to Interact with External Files in Test Scripts

Here we will see how to read data from an external file, write data to an external file, check for the existence of an external file, and delete an external file.

5.17.1.1. How to Read Data from an External File

Reading an external file involves getting its complete filename (including path), and then reading it in the standard way that the scripting language supports. For example:

Python

    infile = findFile("testdata", "before.csv")
    infile = infile.replace("/", os.sep)
    test.log("Reading %s" % infile)
    file = codecs.open(infile, "r", "utf-8")
    lines = []
    for line in file:
        lines.append(line)
    file.close()
    test.verify(len(lines) == 13)
JavaScript

    infile = findFile("testdata", "before.csv");
    infile = infile.replace(/[\/]/g, File.separator);
    test.log("Reading " + infile);
    file = File.open(infile, "r");
    var lines = [];
    var i = 0;
    while (true) {
        var line = file.readln();
        if (line == null)
            break;
        lines[i++] = line;
    }
    file.close();
    test.verify(lines.length == 13);
Perl

    my $sep = File::Spec->rootdir();

    # Load data from an external file
    my $infile = findFile("testdata", "before.csv");
    $infile =~ s,/,$sep,g;
    test::log("Reading $infile");
    open(FILE, "<:encoding(UTF-8)", $infile) or test::fail("Failed to read $infile");
    my @lines = <FILE>;
    close(FILE);
    test::verify(scalar(@lines) == 13);
Ruby

  infile = natify(findFile("testdata", "before.csv"))
  Test.log("Reading %s" % infile)
  lines = []
  File.open(infile, "r:utf-8") do |file|
    file.each {|line| lines << line}
  end
  Test.verify(lines.length == 13)
Tcl

    set infile [file nativename [findFile "testdata" "before.csv"]]
    test log "Reading $infile"
    set fh [open $infile]
    set text [read $fh]
    close $fh
    set text [string trimright $text]
    set lines [split $text "\n"]
    test compare [llength $lines] 13

Here, we read a file called before.csv that is in the suite's (or the test case's) testdata directory. The file is a text file using the UTF-8 encoding. We open the file and read it line by line into a list (or array) of lines or into a string which we then break into lines, depending on the scripting language. And at the end we check that we have got exactly the number of lines we expected.

Squish uses Unix-style path separators internally on all platforms, but because we want to show the path to the user (using the test.log function), we replace these with the path separator that is appropriate for the platform (e.g., “\” on Windows).

JavaScript has no native support for file handling or for operating system interaction, so Squish provides the File Object (Section 6.16.3) and the OS Object (Section 6.16.5) to fill these gaps.

5.17.1.2. How to Write Data to an External File

Writing to an external file is simply a matter of creating a filename, opening the file for writing, and writing data to it in the standard way that the scripting language supports. For example:

Python

    outfile = os.path.join(os.getcwd(), os.path.basename(infile) + ".tmp")
    outfile = outfile.replace("/", os.sep)
    test.log("Writing %s" % outfile)
    file = codecs.open(outfile, "w", "utf-8")
    for line in lines:
        file.write(line)
    file.close()
JavaScript

    outfile = infile + ".tmp";
    var i = outfile.lastIndexOf(File.separator);
    if (i > -1)
        outfile = outfile.substr(i + 1);
    outfile = OS.cwd().replace(/[\/]/g, File.separator) + File.separator + outfile;
    test.log("Writing " + outfile);
    file = File.open(outfile, "w")
    for (var i in lines)
        file.write(lines[i] + "\n");
    file.close();
Perl

    # Save data to an external file
    my $outfile = File::Spec->rel2abs(basename($infile) . ".tmp");
    $outfile =~ s,/,$sep,g;
    test::log("Writing $outfile");
    open(FILE, ">:encoding(UTF-8)", $outfile) or test::fail("Failed to write $outfile");
    print FILE @lines;
    close(FILE);
Ruby

  outfile = natify(File.join(Dir.getwd, File.basename(infile) + ".tmp"))
  Test.log("Writing %s" % outfile)
  File.open(outfile, "w:utf-8") do |file|
    lines.each {|line| file.write(line)}
  end
Tcl

    set outfile [file nativename [file join [pwd] [file tail "$infile.tmp"]]]
    test log "Writing $outfile"
    set fh [open $outfile "w"]
    foreach line $lines {
        puts $fh $line
    }
    close $fh

Here, we write a file that has the same basename as the file we read, but with .tmp appended (e.g., before.csv.tmp), and save it into the script's current working directory. Since we write exactly the same data as we read, this file and the original should be identical. (We'll see how to check this in a later subsection.)

Just as we did when reading a file, we replace the Unix-style path separators with the path separator that is appropriate for the platform. This is done purely for the output to the test.log function; if we are not showing the filename to the user we could safely use Unix path separators no matter what the platform.

5.17.1.3. How to Check the Existence of an External File

Here is an example that checks two files: the first is expected to exist and the second is not expected to exist.

Python

    test.verify(os.path.exists(infile), "infile correctly present")
    test.verify(not os.path.exists(outfile), "outfile sucessfully deleted")
JavaScript

    test.verify(File.exists(infile), "infile correctly present");
    test.verify(!File.exists(outfile), "outfile sucessfully deleted");
Perl

    test::verify(-e $infile, "infile correctly present");
    test::verify(!-e $outfile, "outfile sucessfully deleted");
Ruby

  Test.verify(File.exist?(infile), "infile correctly present")
  Test.verify(!File.exist?(outfile), "outfile sucessfully deleted")
Tcl

    test verify [file exists $infile] "infile correctly present"
    test verify [expr ![file exists $outfile]] "outfile sucessfully deleted"

We have used the two-argument form of the test.verify function to provide more useful detail information than simply “True expression”.

5.17.1.4. How to Remove an External File

Removing an external file is easy—but not reversible!

Python

    os.remove(outfile)

JavaScript

    File.remove(outfile);
Perl

    unlink $outfile;
Ruby

  File.delete(outfile)
Tcl

    file delete $outfile

It would make sense to follow this with a call to the test.verify function in conjunction with an existence test to check that the file has been removed as expected.

5.17.2. How to Compare External Files inside Test Scripts

To compare two external files, for most scripting languages there are two approaches we can take. The easiest approach is to read in the entire contents of each file—for example, into two strings—and compare the strings. Unfortunately this approach can be very slow if the files are large or if lots of files are compared. Another approach—that is likely to be very fast—is to use an external program designed for the job. On Unix-like systems, such a program is diff (“difference”) and on Windows the program is fc (“file compare”).

Python

    if sys.platform in ("win32", "cygwin"):
        command = ["fc"]
    else:
        command = ["diff"]
    command.extend(('"%s"' % infile, '"%s"' % outfile))
    result = subprocess.call(" ".join(command), shell=True)
    test.verify(result == 0, "infile and outfile equal according to %s" % (
JavaScript

    var diff = OS.name == "Windows" ? "fc" : "diff";
    var command = diff;
    command += ' "' + infile + '" "' + outfile + '"';
    var result = OS.system(command);
    test.verify(result == 0,
                "infile and outfile equal according to " + diff);
Perl

    my $diff = $^O eq "MSWin32" ? "fc" : "diff";
    my $command = "$diff \"$infile\" \"$outfile\"";
    system($command);
    my $result = $? >> 8;
    test::verify($result == 0, "infile and outfile equal according to $diff");
Ruby

  return path.gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
end

def main
  startApplication("csvtable")
  # Load data from an external file
  infile = natify(findFile("testdata", "before.csv"))
  Test.log("Reading %s" % infile)
  lines = []
  File.open(infile, "r:utf-8") do |file|
    file.each {|line| lines << line}
  end
  Test.verify(lines.length == 13)
Tcl

    if {$::tcl_platform(platform) eq "windows"} {
        set diff "fc"
    } else {
        set diff "diff"
    }
    set result 0
    if {[catch {exec $diff $infile $outfile} message options]} {
        set details [dict get $options -errorcode]
        if {[lindex $details 0] eq "CHILDSTATUS"} {
            set result [lindex $details 2]
            test fail "infile and outfile not equal according to $diff"
        } else {
            test fail "Failed to get $diff's result"
        }
    } else {
        test pass "infile and outfile equal according to $diff"
    }

We have to make our script account for the fact that we use the fc program on Windows and the diff program on other platforms. Fortunately, both programs exhibit the same behavior: if the two files are the same they return 0 to the operating system (0 is the traditional “success” value for programs) and if the two files differ they return 1. (They may return other values, e.g., 2, if an error in the command line they are given is encountered.)

Note that it is essential that the filenames use the correct path separators for the platform. We also put the filenames in quotes—except for the Tcl example—in case they (or their paths) contain spaces—something quite possible on Windows.

[Note]Python-specific

Python programmers can avoid using an external program and also the inefficiency of loading entire files into memory by taking advantage of the Python standard library's filecmp module. This reduces comparing two files to a single statement:

Python

    test.verify(filecmp.cmp(infile, outfile, False),
        "infile and outfile equal according to filecmp library")

The filecmp.cmp function returns a Boolean with True indicating that the files are the same. The third parameter should always be False to avoid any risk of false-positives.

5.17.3. How to Read Environment Variables inside Test Scripts

Here is an example of how to read some specific environment variables that might be set when the test script is running.

Python

    for key in ("HOME", "PATH", "MY_ENV_VAR"):
        test.log("%s = %s" % (key, os.environ.get(key)))
JavaScript

    var keys = ["HOME", "PATH", "MY_ENV_VAR"];
    for (i in keys)
        test.log(keys[i] + " = " + OS.getenv(keys[i]));
Perl

    for my $key ("HOME", "PATH", "MY_ENV_VAR") {
        test::log("$key = $ENV{$key}");
    }
Ruby

  ENV.each {|key, value| Test.log("#{key} = #{value}") }
Tcl

    global env
    foreach key {"HOME" "PATH" "MY_ENV_VAR"} {
        set value ""
        if {[catch {set value $env($key)}]} {
            # do nothing for missing key: empty default value is fine
        }
        test log "$key = $value"
    }

If you use a shell script or batch file as your AUT you can easily set some test-suite-specific environment variables which you can then access inside your test scripts using the technique shown here. (See Shell Scripts and .bat-Files as AUT (Section 7.3.4).) Note that test scripts do not have access to the AUT's environment variables (e.g., those set in the Settings view (Section 8.2.16)'s Environment section).

Notice that for Python, using the dict.get method ensures that we get None as the value of missing keys rather than an exception. Similarly, for Tcl we achieve the same thing by catching the exception that occurs if the looked up key is missing. For JavaScript and Perl a missing key's value is a harmless empty value which prints as an empty string.