Infrastructural Programs with Progsbase

Martin F. Johansen, 2023-06-10

Computational Programs, A Recap

Computational programs with progsbase are easy to develop and test. They run on a single CPU or thread and use only memory, no other devices like the screen or files etc. To test a computational program, simply call a function. Given sufficient memory and enough time, the functional will always give the same output for the same input.

Take, for example, the function IsValidJSON from the JSON library version 0.5.7. It takes a string as input and returns a boolean and a string as output. The boolean is set to true if the JSON string is valid, and false otherwise. If invalid, then the message is filled with the reason the JSON string is invalid JSON.

StringReference message = ...;
char [] json = ...;
boolean valid = IsValidJSON(json, message);

Infrastructural Programs

An infrastructural program is a program that interacts with other devices than the single CPU and RAM of computational programs. For example, it interacts with a disk or another CPU. Such programs are notoriously difficult to test reliably: They require the devices, such as the disk or an external service, to respond reliably each time a test is run. This makes the tests brittle, and they tend to gradually fail over time, as the services become unavailable.

In progsbase, infrastructural programs are always developed as computational programs. By default, the devices are mocked. This means that the infrastructural programs can be developed and tested in a completely stable environment. If a test has passed once, it will always pass, no changing external factor will ever impact it.

Lets look at an example: a cache. The following is a cache that will cache the responses of a web service. The function ResponseCacheIteration from the ResponseCache library version 0.1.0 implements this functionality.

Here is the signature of the function. A cache sits between two devices: A client, doing the requst, and a server, serving the response. These two devices are passed as inputs to the function. In addition, the cache has a state.

void ResponseCacheIteration(ProcessingUnitServerStructure client, ProcessingUnitStructure server, ResponseCacheState state)

The cache works as follows: It gets a request from the client. If this exact request has not been seen before, i.e. is not in the response cache state, then it forwards the request to the server, gets the response back, stores the request-response pair in its state and returns that response to the client.

The second time the same request comes, the cache sees that it has the response stored. Then, it simply returns the response without calling the server.

We can set up a test for this using our mocks. The client has two requests that are the same. We then run the function ResponseCacheIteration two times. Then we check that the server only had to respond once.

ProcessingUnitServerStructure client = ...
ProcessingUnitStructure server = ...

ResponseCacheInitialize(client, server, 10, 200, state);

ResponseCacheIteration(client, server, state);
ResponseCacheIteration(client, server, state);

AssertEquals(client.responseCount, 2, failures);

AssertEquals(server.requestCount, 1, failures);

Making Actual Infrastructural Programs

In order to make an actual response cache, we need to replace the mocks. Let's use client and server devices interacting with Unix Domain Sockets: A unix socket server, and a unix socket client.

We simply delete the folder with the ProcessingUnit mock, and copy in the Unix Socket Driver. We then set up the infrastructural program as shown below. It always follows the same structure: First the devices are set up. Here they set up unix domain sockets. Then the state is initialized. Finaly there is an infinite loop with the iteration step.

public class ResponseCacheMain {
  public static void main(String[] args) {
    ProcessingUnitServerStructure client = CreateSocketServer("CachedWebSever");
    ProcessingUnitStructure server = CreateSocketClient("WebSever");

    ResponseCacheState state = new ResponseCacheState();
    ResponseCacheInitialize(client, server, 1000, 1000000, state);

      ResponseCacheIteration(client, server, state);

The initialization step initializes a cache consisting of 1000 responses of maximum 10 mega bytes.

The webservice that was previously exposed on the socket named "WebSever" is now exposed as a cached web serivce on the socket "CachedWebSever".

Contact Information

We would be more than happy to help you. Our opening hours are 9–15 (CET).

[email protected]

📞 (+47) 93 68 22 77

Nils Bays vei 50, 0876 Oslo, Norway

Copyright © 2018-23 by Inductive AS.