Executables for PBT

Executables are how software programs run and interact with other software. Programming their interaction is a useful skill to have-as is being able to pick up the specific syntax for process interactions within a new programming language by using the language's documentation.

Here, we'll go over some of the basics, but I encourage you to reference online documentation and tutorials liberally for this part!

You should follow along for either Python or Rust here, not both.

Python

Python comes pre-installed on CS Linux and on Macs. If you would like to install Python in your own computer and have not yet, grab version 3.X from the Python website.

Python's official online documentation also provides an introduction.

Rust

Install Rust (any version, default to current) from the Rust website.

Making Executables

For Property-Based Testing (PBT), you need to create two executable programs: topsort and oracle.

Files need executable permissions in order to be executed (figures). To set executable permissions, run

chmod +x topsort

and

chmod +x oracle

To learn the details of Unix permissions, we recommend reviewing the relevant parts of this Wikipedia article.

Python

To make a file executable, we must provide an interpreter directive (colloquially known as the "hashbang" on Unix machines). You'll want to create the files topsort and oracle (note: no .py in the name) and temporarily add the following lines to the top of each:

#!/usr/bin/python3

print("Hello World!")

To test your programs, run ./topsort and ./oracle.

The #! line serves as the interpreter directive, and the Python interpreter will run the file's code when executed.

Rust

For Rust, the Rust Book has a great walkthrough of creating a project with an executable.

You can choose whether or not to create two separate Rust projects, one per executable, or to put both executables in the same project.

To make two separate project, run:

cargo new topsort
cargo new oracle

To create one project (where you'll need to later rename the executable), run:

cargo new pbt

cd inside the project directory (say, topsort) and run cargo build to create your executable.

The executable will be created in ./target/debug/topsort. You can mv the executable to your project directory for ease of calling it (or you can just pass the full path to other commands).

If you want to use one project with two executables (which is slightly better style, but will not be graded differently for this project), check out the crate documentation. You'll want two binary crates inside your single project, one for each executable.

Command Line Arguments

Python

Python's sys module provides access to command line arguments. To import the module, add import sys to your program. The expression

sys.argv

produces a list of strings corresponding to command line arguments. Note that the program's name comprises the first element of this list, so sys.argv[1] returns the first user-specified command line argument.

Rust

Rust's corresponding crate is also named sys, and you can import it with use std::env; (see the book chapter).

You can then access the arguments with:

let args: Vec<String> = env::args().collect();

Standard I/O

Python

Python's libraries and built-in functions offer several ways of accessing standard input and output. We suggest accessing standard input and output through the sys module, also used for accessing command line arguments.

sys.stdin.readlines()

produces a list of strings corresponding to each line of standard input.

Similarly,

sys.stdout.write("Hello World!")

writes "Hello World!" to standard output (as does print("Hello World!")).

Rust

You'll want to import use std::fs; to interact with the filesystem. Then, for example (from the book chapter):

let contents = fs::read_to_string(file_path)
    .expect("Should have been able to read the file");

The simplest way to write to standard out is with println!(...). See the documentation for writing multiple lines if needed.

Opening a new process

For your oracle executable to evaluate your topsort executable, the oracle process needs to run topsort as a subprocess (relevant CS240 slides). As you may recall from CS240, there are specific ways for processes to communicate. When a parents and child process need to communicate only at the start and end of the child process, the easiest way to communicate is by "piping" data between their standard input and output.

Each of these streams of data can consist of raw bytes, but in this case, we'll write strings of data with the graph input/output text formatting described in the assignment.

Python

Python's subprocess module provides an interface for running other processes from within a program. For example, in oracle, you'll need to run a topsort program. To import the module, add import subprocess to your program. To start a process and bind its handle to a variable, use a statement of the following form:

import subprocess
process = subprocess.Popen(
    ["/usr/bin/tsort"], 
    stdin=subprocess.PIPE, 
    stdout=subprocess.PIPE, 
    stderr=subprocess.PIPE
)

Additional arguments to the process follow in the list of the first argument. One way to write to the standard input of process is to use the communicate method:

in_str = "1 2\n3 4\n"
in_data = in_str.encode('utf-8')
(out_data, err_data) = process.communicate(in_data)
out_str = out_data.decode('utf-8')
err_str = err_data.decode('utf-8')

out_str contains the standard output and err_str contains the standard error.

Rust

Here is a modified example from the documentation of starting a subprocess (in this case, the ls utility with argument -a):

let mut p = Popen::create(&["ls", "-a"], PopenConfig {
    stdout: Redirection::Pipe, ..Default::default()
})?;

// Obtain the output from the standard streams.
let (out, err) = p.communicate(None)?;

out contains the standard output and err contains the standard error.

Running from Command Line

Suppose you have a file named myfile.txt with the following content:

7 11
7 8
3 10
5 11
3 8
8 9
11 10
11 2
11 9

To run your topsort or your system's built-in tsort program with this data, use a command of the following form to redirect the contents of the file as standard input:

./topsort  < myfile.txt

Similarly, to invoke your oracle, run:

./oracle /usr/bin/tsort 50
./oracle ./topsort 50

Attribution

The Python components of this guide are cribbed directly from Brown University's Logic for Systems Lab 0. Thank you to the Brown UTAs for writing it (including but not limited to past Alexa circa 2016 :wink:).