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:
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()
})?;
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
).
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:
topsortandoracle.Files need executable permissions in order to be executed (figures). To set executable permissions, run
and
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
topsortandoracle(note: no.pyin the name) and temporarily add the following lines to the top of each:#!/usr/bin/python3 print("Hello World!")To test your programs, run
./topsortand./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:
To create one project (where you'll need to later rename the executable), run:
cdinside the project directory (say,topsort) and runcargo buildto create your executable.The executable will be created in
./target/debug/topsort. You canmvthe 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
cratedocumentation. You'll want two binary crates inside your single project, one for each executable.Command Line Arguments
Python
Python's
sysmodule provides access to command line arguments. To import the module, addimport systo your program. The expressionproduces 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 withuse std::env;(see the book chapter).You can then access the arguments with:
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
sysmodule, also used for accessing command line arguments.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):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
oracleexecutable to evaluate yourtopsortexecutable, theoracleprocess needs to runtopsortas 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
subprocessmodule provides an interface for running other processes from within a program. For example, inoracle, you'll need to run a topsort program. To import the module, addimport subprocessto 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
processis to use thecommunicatemethod: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_strcontains the standard output anderr_strcontains the standard error.Rust
Here is a modified example from the documentation of starting a subprocess (in this case, the
lsutility with argument-a):outcontains the standard output anderrcontains the standard error.Running from Command Line
Suppose you have a file named
myfile.txtwith the following content:To run your
topsortor your system's built-intsortprogram with this data, use a command of the following form to redirect the contents of the file as standard input:Similarly, to invoke your
oracle, run: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
).