Skip to content

bolsen/pickcheck

Repository files navigation

PickCheck : QuickCheck for Free Pascal

Note: This is in development. It's in a proof-of-concept stage, so there is a bunch of things to do to make it useful.

This is an implementation of QuickCheck for Free Pascal (Object Pascal) loosely based on Douglas Crockford's JSCheck and a fluid interface influenced by jetCheck for Java. It's not complete, so the readme here now has caveats on the API design.

The idea of QuickCheck originated for Haskell in this paper: https://dl.acm.org/doi/10.1145/351240.351266. It is intended to test programs to see if it follows properties, whether with fixed or random data.

There is a light and verbose interface. The verbose interface is the actual implementation, but it might be quicker to throw around property checks that are a little opinionated (this comes from jetCheck, which is a Java version of QuickCheck.)

The heaviest bit here is whether generics are actually needed. The type you are declaring for the generic classes is the primary input type (but values can be wrapped in composite types.) The predicates center around testing the validity of the inputs.

Compile

Since this is using a lot of new features, FP 3.3.1 is needed.

There is nothing special at the moment to do other than to run the test:

fpc tests.lpr && ./tests

There is a package file for Lazarus also. To install it:

lazbuild --add-package pickcheck.lpk

Example

Start with some simple examples.

var
  suite: specialize TCheckPropertySuite<Integer>;
  reporter: specialize TCheckPropertyConsoleReporter<Integer>;

...

try
  runner := specialize PropertyChecker<Integer>.
    ForAll([specialize GenNumber<Integer>(1,1000), specialize GenNumber<Integer>(1,1000)],
           function(value: array of Integer): Boolean
             begin
               Result := value[0] > value[1];
             end).
    WithName('Test for greater-than').
    WithNumberofTrials(100)
    Check;

    reporter := specialize TCheckPropertyConsoleReporter<Integer>.Create(runner.Report[0], runner.Options);
    reporter.DoReport;

finally
    runner.Free;
end;

The Check function returns a TCheckPropertySuite object, which you can then use to inspect the output.

In the example, we want to check two numbers and find any case where the first value is not the same as the second value. It will be quick to see that with random data, it will falsify the statement. This is an output of the run:

Test for greater-than: Falsification after 1 tests.
Failing input was :
638
994

If all checks pass:

Test for greater-than: All tests passed!

With real software, we want to aim for 100% un-falsifiability if we can. What is nice is that random data can serve towards heuristic testing, where repeated runs will produce diminishing returns in finding bugs.

You can see more examples in pickcheck.examples.pas.

A more complex example:

type
  TPerson = record
    name: String;
    age: String;
   end;

function MakePerson: specialize TSpecifierGeneratorFunc<TPerson>;
begin
    Result := function()
    begin
        with Result do
        begin
            name := GenString()();
            age  := GenNumber(1, 120)();
        end;
    end;
end;

try
  runner := specialize PropertyChecker<TPerson>.
    ForAll([MakePerson()],
           function(value: array of TPerson): Boolean
             begin
               Result := value[0].age < 120;
             end).
    Check;
finally
    runner.Free;
end;

Verbose interface

The PropertyChecker just spins up all of the things to check one property, outputing a special sub-class of TCheckPropertySuite. This class can support more properties as a suite and more customization, but the PropertyChecker should suffice for most cases and ideally in the future, do everything.

type
  TBalanceOrderProperty = class (specialize TCheckPropertyBuilder<Integer>)
  public
   function Predicate(value: array of Integer): Boolean; override;
   function Classify(value: array of Integer): String; override;
  end;

implementation

function TMyCheckPropertyBuilder.Predicate(value: array of TOrder): Boolean;
begin
    if value[0].Side = otBuy then
    begin
      Result :=
      (value[0].FillAmount = value[0].Amount) and (value[1].FillAmount = 0);
  end else if value[0].Side = otSell then
      Result := (value[0].FillAmount = 0) and (value[0].FillAmount = value[0].Amount);

end;

function TMyCheckPropertyBuilder.Classify(value: array of Integer): String;
begin
...
end;

License and copyright

© 2025 Brian Olsen. Licensed under the LGPL License. For more information, view the LICENSE file.

About

Property-based testing in Free Pascal, ala QuickCheck (mirror of: https://gitlab.com/bolsen80/pickcheck)

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages