A bytecode-based virtual machine to implement scripting/filtering support in your golang project.

GoDoc Go Report Card license

eval-filter

The evalfilter package provides an embeddable evaluation-engine, which allows simple logic which might otherwise be hardwired into your golang application to be delegated to (user-written) script(s).

There is no shortage of embeddable languages which are available to the golang world, this library is intended to be something that is:

  • Simple to embed.
  • Simple to use, as there are only three methods you need to call:
  • Simple to understand.
  • As fast as it can be, without being too magical.

The scripting language is C-like, and is generally intended to allow you to filter objects, which means you might call the same script upon multiple objects, and the script will return either true or false as appropriate to denote whether some action might be taken by your application against that particular object.

It certainly is possible for you to handle arbitrary return-values from the script(s) you execute, and indeed the script itself could call back into your application to carry out tasks, via the addition of new primitives implemented and exported by your host application, which would make the return value almost irrelevant.

If you go down that route then this repository contains a general-purpose scripting-language, which can be used to execute user-supplied scripts.

My Google GMail message labeller uses the evalfilter in such a standalone manner, executing a script for each new/unread email by default. The script can then add labels to messages based upon their sender/recipients/subjects. etc. The notion of filtering there doesn't make sense, it just wants to execute flexible operations on messages.

However the ideal use-case, for which this was designed, is that your application receives objects of some kind, perhaps as a result of incoming webhook submissions, network events, or similar, and you wish to decide how to handle those objects in a flexible fashion.

Implementation

In terms of implementation the script to be executed is split into tokens by the lexer, then those tokens are parsed into an abstract-syntax-tree. Once the AST exists it is walked by the compiler and a series of bytecode instructions are generated.

Once the bytecode has been generated it can be executed multiple times, there is no state which needs to be maintained, which makes actually executing the script (i.e. running the bytecode) a fast process.

At execution-time the bytecode which was generated is interpreted by a simple virtual machine. The virtual machine is fairly naive implementation of a stack-based virtual machine, with some runtime support to provide the builtin-functions, as well as supporting the addition of host-specific functions.

The bytecode itself is documented briefly in BYTECODE.md, but it is not something you should need to understand to use the library, only if you're interested in debugging a misbehaving script.

Scripting Facilities

Types

The scripting-language this package presents supports the basic types you'd expect:

  • Arrays.
  • Floating-point numbers.
  • Hashes.
  • Integers.
  • Regular expressions.
  • Strings.
  • Time / Date values.
    • i.e. We can use reflection to handle time.Time values in any structure/map we're operating upon.

The types are supported both in the language itself, and in the reflection-layer which is used to allow the script access to fields in the Golang object/map you supply to it.

Built-In Functions

These are the built-in functions which are always available, though your users can write their own functions within the language (see functions).

You can also easily add new primitives to the engine, by defining a function in your golang application and exporting it to the scripting-environment. For example the print function to generate output from your script is just a simple function implemented in Golang and exported to the environment. (This is true of all the built-in functions, which are registered by default.)

  • float(value)
    • Tries to convert the value to a floating-point number, returns Null on failure.
    • e.g. float("3.13").
  • getenv(value)
    • Return the value of the named environmental variable, or "" if not found.
  • int(value)
    • Tries to convert the value to an integer, returns Null on failure.
    • e.g. int("3").
  • keys
    • Returns the available keys in the specified hash, in sorted order.
  • len(field | value)
    • Returns the length of the given value, or the contents of the given field.
    • For arrays it returns the number of elements, as you'd expect.
  • lower(field | value)
    • Return the lower-case version of the given input.
  • print(field|value [, fieldN|valueN] )
    • Print the given values.
  • printf("Format string ..", arg1, arg2 .. argN);
    • Print the given values, with the specified golang format string
      • For example printf("%s %d %t\n", "Steve", 9 / 3 , ! false );
  • reverse(["Surname", "Forename"]);
    • Sorts the given array in reverse.
    • Add true as the second argument to ignore case.
  • sort(["Surname", "Forename"]);
    • Sorts the given array.
    • Add true as the second argument to ignore case.
  • split("string", "value");
    • Splits a string into an array, by the given substring..
  • sprintf("Format string ..", arg1, arg2 .. argN);
    • Format the given values, using the specified golang format string.
  • string( )
    • Converts a value to a string. e.g. "string(3/3.4)".
  • trim(field | string)
    • Returns the given string, or the contents of the given field, with leading/trailing whitespace removed.
  • type(field | value)
    • Returns the type of the given field, as a string.
      • For example string, integer, float, array, boolean, or null.
  • upper(field | value)
    • Return the upper-case version of the given input.
  • hour(field|value), minute(field:value), seconds(field:value
    • Allow converting a time to HH:MM:SS.
  • day(field|value), month(field:value), year(field:value
    • Allow converting a time to DD/MM/YYYY.
  • weekday(field|value)
    • Allow converting a time to "Saturday", "Sunday", etc.
  • now() & time() both return the current time.

Conditionals

As you'd expect the facilities are pretty normal/expected:

  • Perform comparisons of strings and numbers:
    • equality:
      • "if ( Message == "test" ) { return true; }"
    • inequality:
      • "if ( Count != 3 ) { return true; }"
    • size (<, <=, >, >=):
      • "if ( Count >= 10 ) { return false; }"
      • "if ( Hour >= 8 && Hour <= 17 ) { return false; }"
    • String matching against a regular expression:
      • "if ( Content ~= /needle/ )"
      • "if ( Content ~= /needle/i )"
        • With case insensitivity
    • Does not match a regular expression:
      • "if ( Content !~ /some text we don't want/ )"
    • Test if an array contains a value:
      • "return ( Name in [ "Alice", "Bob", "Chris" ] );"
  • Ternary expressions are also supported - but nesting them is a syntax error!
    • "a = Title ? Title : Subject;"
    • "return( result == 3 ? "Three" : "Four!" );"

Loops

Our script implements a golang-style loop, using either for or while as the keyword:

count = 0;
while ( count < 10 ) {
     print( "Count: ", count, "\n" );
     count++;
}

You could use either statement to iterate over an array contents, but that would be a little inefficient:

items = [ "Some", "Content", "Here" ];
i = 0;
for ( i < len(items) ) {
   print( items[i], "\n" );
   i++
}

A more efficient and readable approach is to iterate over arrays, and the characters inside a string, via foreach. You can receive both the index and the item at each step of the iteration like so:

foreach index, item in [ "My", "name", "is", "Steve" ] {
    printf( "%d: %s\n", index, item );
}

If you don't supply an index you'll receive just the item being iterated over instead, as you would expect (i.e. we don't default to returning the index, but the value in this case):

len = 0;
foreach char in "็‹็Šฌ" {
    len++;
}
return( len == 2 );

The same kind of iteration works over hashes too (the single-argument version of the foreach loop iterates over values, rather than keys. Hash keys are available via keys so that seems like a more useful thing to return):

foreach key,value in { "Name": "Steve", "Location": "Finland" } {
  printf("Key %s has value %s\n", key, value );
}

The final helper is the ability to create arrays of integers via the .. primitive:

sum = 0;
foreach item in 1..10 {
    sum += item;
}
print( "Sum is ", sum, "\n" );

Here you note that len++ and sum += item; work as you'd expect. There is support for +=, -=, *=, and /=. The ++ and -- postfix operators are both available (for integers and floating-point numbers).

Functions

You can declare functions, for example:

function sum( input ) {
   local result;
   result = 0;
   foreach item in input {
     result = result + item;
   }
   return result;
}

printf("Sum is %d\n", sum(1..10));
return false;

See _examples/scripts/scope.in for another brief example, and discussion of scopes.

Case / Switch

We support the use of switch and case to simplify the handling of some control-flow. An example would look like this:

switch( Subject ) {
  case /^Re:/i {
     printf("Reply\n");
  }
  case /^fwd:/i {
     printf("Forwarded message\n");
  }
  case "DEAR" + "  " + WINNER" {
     printf("SPAM\n");
  }
  case "YOU HAVE WON" {
     printf("SPAM\n");
  }
  default {
     printf("New message!\n");
  }
}

Note that the case expression supports the following, as demonstrated in our switch example:

  • Expression matches.
  • Literal matches.
  • Regular expression matches.

To avoid fall-through-related bugs we've explicitly designed the case-statements to take blocks as arguments, rather than statements.

NOTE: Only the first matching case-statement will execute. In the following example only one message will be output:

count = 1;

switch( count ) {
  case 1 {
     printf("Match!\n");
  }
  case 1 {
     printf("This is not a match - the previous case statement won!\n");
  }
}

Use Cases

The motivation for this project came from a problem encountered while working:

  • I wanted to implement a simple "on-call notifier".
    • When messages were posted to Slack channels I wanted to sometimes trigger a phone-call to the on-call engineer.
    • Of course not all Slack-messages were worth waking up an engineer for..

The expectation was that non-developers might want to change the matching of messages to update the messages which were deemed worthy of waking up the on-call engineer. They shouldn't need to worry about rebuilding the on-call application, nor should they need to understand Go. So the logic was moved into a script and this evaluation engine was born.

Each time a Slack message was received it would be placed into a simple structure:

type Message struct {
    Author  string
    Channel string
    Message string
    Sent    time.Time
}

Then a simple script could then be executed against that object to decide whether to initiate a phone-call:

//
// You can see that comments are prefixed with "//".
//
// In my application a phone-call would be trigged if this
// script hit `return true;`.  If the return value was `false`
// then nothing would happen.
//

//
// If this is within office hours we'll assume somebody is around to
// handle the issue, so there is no need to raise a call.
//
if ( hour(Sent) >= 9 || hour(Sent) <= 17 ) {

    // 09AM - 5PM == Working day.  No need to notify anybody.

    // Unless it is a weekend, of course!
    if ( weekday(Sent) != "Saturday" && weekday(Sent) != "Sunday" ) {
       return false;
    }
}

//
// A service crashed with a panic?
//
// If so raise the engineer.
//
if ( Message ~=  /panic/i ) { return true; }


//
// At this point we decide the message is not important, so
// we ignore it.
//
// In a real-life implementation we'd probably work the other
// way round.  Default to triggering the call unless we knew
// it was a low-priority/irrelevant message.
//
return false;

You'll notice that we test fields such as Sent and Message here which come from the object we were given. That works due to the magic of reflection. Similarly we called a number of built-in functions related to time/date. These functions understand the golang time.Time type, from which the Sent value was read via reflection.

(All time.Time values are converted to seconds-past the Unix Epoch, but you can retrieve all the appropriate fields via hour(), minute(), day(), year(), weekday(), etc, as you would expect. Using them literally will return the Epoch value.)

Security

The user-supplied script is parsed and turned into a set of bytecode-instructions which are then executed. The bytecode instruction set is pretty minimal, and specifically has zero access to:

  • Your filesystem.
    • i.e. Reading files is not possible, neither is writing them.
  • The network.
    • i.e. Making outgoing network requests is not possible.

Of course you can export functions from your host-application to the scripting environment, to allow such things. If you do add primitives that have the possibility to cause security problems then the onus is definitely on you to make sure such accesses are either heavily audited or restricted appropriately.

Denial of Service

When it comes to security problems the most obvious issue we might suffer from is denial-of-service attacks; it is certainly possible for this library to be given faulty programs, for example invalid syntax, or references to undefined functions. Failures such as those would be detected at parse/run time, as appropriate.

In short running user-supplied scripts should be safe, but there is one obvious exception, the following program is valid:

print( "Hello, I'm wasting your time\n") ;

while( 1 ) {
  // Do nothing ..
}

print( "I'm never reached!\n" );

This program will never terminate! If you're handling untrusted user-scripts, you'll want to ensure that you explicitly setup a timeout period.

The following will do what you expect:

// Create the evaluator on the (malicious) script
eval := evalfilter.New(`while( 1 ) { } `)

// Setup a timeout period of five seconds
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
eval.SetContext(ctx)

// Now prepare as usual
err = eval.Prepare()
if ( err != nil ) { // handle error }

// Now execute as usual
ret, err = eval.Execute( object )
if ( err != nil ) { // handle error }

The program will be terminated with an error after five seconds, which means that your host application will continue to run rather than being blocked forever!

Sample Usage

To give you a quick feel for how things look you could consult the following simple examples:

  • example_test.go.
    • This filters a list of people by their age.
  • example_function_test.go.
    • This exports a function from the golang-host application to the scripting environment.
      • This is a demonstration of how you'd provide extra functionality when embedding the engine in your own application.
    • The new function is then used to filter a list of people.
  • example_user_defined_function_test.go
    • Writing a function within the scripting-environment, and then calling it.
  • _examples/embedded/variable/
    • Shows how to pass a variable back and forth between your host application and the scripting environment

Additional Examples

Additional examples of using the library to embed scripting support into simple host applications are available beneath the _examples/embedded directory.

There are also sample scripts contained beneath _examples/scripts which you can examine.

The standalone driver located beneath cmd/evalfilter allows you to examine bytecode, tokens, and run the example scripts, as documented later in this README file.

Finally if you want to compile this library to webassembly, and use it in a web-page that is also possible! See wasm/ for details.

Standalone Use

If you wish to experiment with script-syntax, after looking at the example scripts you can install the standalone driver:

go get github.com/skx/evalfilter/v2/cmd/evalfilter

This driver, contained within the repository at cmd/evalfilter has a number of sub-commands to allow you to experiment with the scripting environment:

  • Output a dissassembly of the bytecode instructions the compiler generated when preparing your script.
  • Run a script.
    • Optionally with a JSON object as input.
  • View the lexer and parser outputs.

Help is available by running evalfilter help, and the sub-commands are documented thoroughly, along with sample output.

TAB-completion is supported if you're running bash, execute the following to enable it:

$ source <(evalfilter bash-completion)

Benchmarking

The scripting language should be fast enough for most purposes; it will certainly cope well with running simple scripts for every incoming HTTP-request, for example. If you wish to test the speed there are some local benchmarks available.

You can run the benchmarks as follows:

go test -test.bench=evalfilter_ -benchtime=10s -run=^t
goos: linux
goarch: amd64
pkg: github.com/skx/evalfilter/v2
Benchmark_evalfilter_complex_map-4   	 4426123	      2721 ns/op
Benchmark_evalfilter_complex_obj-4   	 7657472	      1561 ns/op
Benchmark_evalfilter_simple-4        	15309301	       818 ns/op
Benchmark_evalfilter_trivial-4       	100000000	       105 ns/op
PASS
ok  	github.com/skx/evalfilter/v2	52.258s

The examples there are not particularly representative, but they will give you an idea of the general speed. In the real world the speed of the evaluation engine is unlikely to be a significant bottleneck.

One interesting thing that shows up clearly is that working with a struct is significantly faster than working with a map. I can only assume that the reflection overhead is shorter there, but I don't know why.

Fuzz Testing

Fuzz-testing is basically magic - you run your program with random input, which stress-tests it and frequently exposes corner-cases you've not considered.

This project has been fuzz-tested repeatedly, and FUZZING.md contains notes on how you can carry out testing of your own.

API Stability

The API will remain as-is for given major release number, so far we've had we've had two major releases:

  • 1.x.x
    • The initial implementation which parsed script into an AST then walked it.
  • 2.x.x
    • The updated design which parses the given script into an AST, then generates bytecode to execute when the script is actually run.

The second release was implemented to perform a significant speedup for the case where the same script might be reused multiple times.

See Also

This repository was put together after experimenting with a scripting language, and writing a BASIC interpreter along with a FORTH interpreter.

I've also played around with a couple of compilers which might be interesting to refer to:

Github Setup

This repository is configured to run tests upon every commit, and when pull-requests are created/updated. The testing is carried out via .github/run-tests.sh which is used by the github-action-tester action.

Steve

Comments
  • a method to serialize object.Object into a JSON compatible struct

    a method to serialize object.Object into a JSON compatible struct

    With eval.Execute returning the actual object.Object, it would be very useful to have a way to convert the object.Object into a struct that can be serialized as JSON.

    A simple eval script can then be used to create JSON views by plucking the data from the object, and returning a different view of the data, like in this sample script:

    if (name == "Dan") {
      return {
        "first_name": name,
       "city": address.city,
      };
    }
    

    Even though my actual object contains significantly more data, in this particular case, it'd only be a JSON object that returns a first name and a city.

  • catching an error within a script

    catching an error within a script

    So, I was using the latest version of Eval Filter and I was attempting to process some JSON data. The data should be consistent in terms of the fields that it contains, but in some cases, they are not. So, this simple script,

    counter = 0;
    if foo.bar == true {
        counter++;
    }
    

    results in the following panic if foo or foo.bar is not present

    panic: reflect: call of reflect.Value.Interface on zero Value
    

    Is there a way we could support a try/catch? This would allow the system to raise an error, and then the caller can choose what to do with the error by handling it or ignoring it depending on their use case.

  • 130 user defined functions

    130 user defined functions

    Once complete this pull-request will allow users to define their own functions, via the new function keyword:

         function foo() {
             return true;
         }
    
    
         if ( foo() ) {
             printf("All is OK\n" );
         }
         return 1;
    

    This will close #130.

  • Examine similar projects

    Examine similar projects

    Here's a small list of similar/related projects I could take inspiration from:

    • https://github.com/benhoyt/littlelang/
      • Small language, contains a sub-implementation of itself.
    • https://github.com/antonmedv/expr
      • Bytecode-based evaluation engine.
      • Wins all the benchmarks!
        • https://github.com/antonmedv/golang-expression-evaluation-comparison
    • https://github.com/haifenghuang/magpie
      • Simple language.
    • Golem-Language is another bytecode-based virtual machine
      • https://github.com/mjarmy/golem-lang
      • Notably has support for switch-statements.
    • https://github.com/d5/tengo/
      • Another small scripting language.
    • https://github.com/Knetic/govaluate
      • Another expression engine.
  • Benchmarking ..

    Benchmarking ..

    There is an expression engine comparison repository which allows benchmarking different projects.

    I added this to benchmark evalfilter:

    
    package main
    
    import (
    	"testing"
    
    	"github.com/skx/evalfilter"
    )
    
    func Benchmark_evalfilter(b *testing.B) {
    	params := createParams()
    
    	eval := evalfilter.New(example)
    
    	var ret bool
    	var err error
    	b.ResetTimer()
    	for n := 0; n < b.N; n++ {
    		ret, err = eval.Run(params)
    	}
    	b.StopTimer()
    
    	if err != nil {
    		b.Fatal(err)
    	}
    	if !ret {
    		b.Fail()
    	}
    }
    

    The results were terrible! This project wasn't the slowest, but damn near!

    $ go test -bench=. -benchtime=5s
    Benchmark_bexpr-4              	 8790668	       686 ns/op
    Benchmark_celgo-4              	18292764	       333 ns/op
    Benchmark_celgo_startswith-4   	13063237	       467 ns/op
    Benchmark_evalfilter-4         	 1872409	      3209 ns/op
    Benchmark_expr-4               	32405442	       197 ns/op
    Benchmark_expr_startswith-4    	16776326	       348 ns/op
    Benchmark_goja-4               	14103356	       359 ns/op
    Benchmark_govaluate-4          	16351401	       375 ns/op
    Benchmark_gval-4               	  653475	      9432 ns/op
    Benchmark_otto-4               	 6175132	       985 ns/op
    Benchmark_starlark-4           	 1000000	      5960 ns/op
    

    I don't expect significant improvements without a major overhaul, but noting here for minor tweaks & documentation-purposes.

  • Second attempt at nested fields

    Second attempt at nested fields

    This is a work in-progress which moves towards a simpler solution for nested field access.

    The realization that we need to only handle the case of maps, and allow parsing them to our internal hash-objects.

    If we have hash-objects parsed correctly then we can add the "." operator as a synonym, so these two lines of code are identical:

       foo["bar"]
       foo.bar
    

    The initial commit makes that change, and followups will handle the parsing.

    This replaces #161, and will close #159.

  • 159 nested access

    159 nested access

    This pull-request features a major rewrite of our reflection code, with the intention that we can access nested fields.

    For example given the following input.json:

    {
        "name": {
            "forename": "John",
            "surname": {
                "value": "Smith"
            }
        },
        "age":30
    }
    

    And the following script:

    
    print( "The age is ", age, "\n" );
    print( "The FORENAME is ", name.forename, "\n" );
    print( "The SURENAME is ", name.surname.value, "\n" );
    
    return false;
    

    We can get the expected output:

    go build . ; ./evalfilter run -json ../../_examples/scripts/on-call.json  ../../_examples/scripts/on-call.script 
    The age is 30
    The FORENAME is John
    The SURENAME is Smith
    Script gave result type:BOOLEAN value:false - which is 'false'.
    

    This has only been lightly tested so far, but the existing test-cases continue to work so that's a reassuring sign.

    The only caveat here is that we internally use JSON encoding and then decoding. This causes two problems:

    • Slower than reflection.
    • Means private fields (i.e. lower-case member names) are ignored.

    Whether those are problems will remain to be seen.

    Closes #159.

  • Support hashes ..

    Support hashes ..

    Hashes are an obvious data-structure to add, and the changes won't be too hard to add:

    • Hashes can be keyed by "string" or "number".
    • Hashes can be declared literally.
    • Hashes might be returned by some built-ins.

    Changes to be made, in brief:

    • [x] The parser needs to recognize a hash literal, at least.
    • [x] We need a Hash-object.
    • [x] The run-time needs to be updated to handle hash-objects. e.g. foreach.
    • [x] The run-time built-ins such as len, need update.
    • [x] We need a new "keys" built-in - or defer to foreach.
    • [x] The VM needs an OpHash to initialize a hash, at least.

    Test-case coverage should be high. We're in good shape there generally.

  • We need to handle scoped variables

    We need to handle scoped variables

    This will be more important if we allow users to define functions, but we still have an issue with the way we declare loop-variables when iterating over arrays/strings.

    Consider this code:

    
    name = "Steve";
    print( "name starts as :", name, "\n");
    print( "index starts as:", index, "\n");
    
    foreach index,name in [ "Bob", "Chris" ] {
      printf( "%d -> %s\n", index, name );
    }
    
    //
    // BUG #1:
    //   name here is "Chris", not "Steve".
    //
    // BUG #2 / Leak:
    //   index here is 1
    //
    print( "Name is now: ", name, "\n" );
    print( "index is now :", index, "\n");
    
    return 0;
    

    Leaking the index variable is annoying, but probably safe.

    Changing the contents of the name variable is definitely an outright bug.

  • We should support a `range` operation.

    We should support a `range` operation.

    Given a structure:

     type Message struct {
           Labels []string
     }
    

    We can run a script which iterates over the items:

       print("\tThe message has ", len(Labels), " labels.\n");
       i = 0;
       while( i < len(Labels) ) {
         print("\tLabel ", i+1 , " is " , Labels[i], "\n");
         i = i + 1;
       }
    

    But that's gross.

    We should support a range operator:

     foreach index, entry  Labels  {    
            print("Label ", i, " is ", entry );
     }
    

    Syntax is somewhat open, but the important thing is that we get access to the index, the entry, and users don't notice and complain about the lack of i++ support ;)

  • 77 arrays

    77 arrays

    When this pull-request is complete we'll have support for arrays, which will close #77:

    • Support the use of reflection to read array-values from objects/maps
    • Support the use of arrays inside user-scripts.

    Current state is that we can lex/parse some array-specific items, but using them will crash as the AST is not compiled. Nor is the VM ready for them. Nor do the built-in functions support them. (e.g. len needs updating.)

A clipboard-based mocking framework for Go that gets out of your way.
A clipboard-based mocking framework for Go that gets out of your way.

A clipboard-based mocking framework for Go that gets out of your way. This tool has been built with inspiration lovingly taken from Moq, and fuelled b

Nov 18, 2022
A collection of packages to augment the go testing package and support common patterns.

gotest.tools A collection of packages to augment testing and support common patterns. Usage With Go modules enabled (go1.11+) $ go get gotest.tools/v3

Jan 4, 2023
Continuous Benchmark for Go Project
Continuous Benchmark for Go Project

Abstract cob compares benchmarks between the latest commit (HEAD) and the previous commit (HEAD{@1}). The program will fail if the change in score is

Dec 13, 2022
Project test case recruitment kanggo

Kanggo Project test case recruitment kanggo Installation Need golang to run go run main.go .env file will be served Admin Account email:[email protected]

Nov 20, 2021
Backpulse's core. Backpulse is an API Based CMS. Build you own website without worrying about the content administration system.

Backpulse core Backpulse is an API Based / Headless CMS. Your site's content is accessible directly via our RESTful API, on any web framework and any

Sep 11, 2022
go-wrk - a HTTP benchmarking tool based in spirit on the excellent wrk tool (https://github.com/wg/wrk)

go-wrk - an HTTP benchmarking tool go-wrk is a modern HTTP benchmarking tool capable of generating significant load when run on a single multi-core CP

Jan 5, 2023
Markdown based document-driven RESTful API testing.
Markdown based document-driven RESTful API testing.

silk Markdown based document-driven web API testing. Write nice looking Markdown documentation (like this), and then run it using the silk command Sim

Dec 18, 2022
Aquatone is a tool for visual inspection of websites across a large amount of hosts and is convenient for quickly gaining an overview of HTTP-based attack surface.

Aquatone is a tool for visual inspection of websites across a large amount of hosts and is convenient for quickly gaining an overview of HTTP-based attack surface.

Jan 6, 2023
๐Ÿ“ก mock is a simple, cross-platform, cli app to simulate HTTP-based APIs.
 ๐Ÿ“ก mock is a simple, cross-platform, cli app to simulate HTTP-based APIs.

mock ?? mock is a simple, cross-platform, cli app to simulate HTTP-based APIs. About mock Mock allows you to spin up a local http server based of a .m

May 6, 2022
Package has tool to generate workload for vegeta based kube-api stress tests.

Package has tool to generate workload for vegeta based kube-api stress tests.

Nov 22, 2021
Simple mock program to set charging rate of a battery instance based on the national grid intensity api

Charger Simple mock program to set charging rate of a battery instance based on the national grid intensity api. Steps to get up and running I have cr

Nov 16, 2021
Library for internal Go based services in Wimark

libwimark Library with main models, functions and bindings, using in Golang's mi

Dec 29, 2021
Gherkin-based BDD testing for go

gocuke ?? gocuke is a Gherkin-based BDD testing library for golang. Features tight integration with *testing.T (use any standard assertion library or

Jun 22, 2022
Test your command line interfaces on windows, linux and osx and nodes viรก ssh and docker

Commander Define language independent tests for your command line scripts and programs in simple yaml files. It runs on windows, osx and linux It can

Dec 17, 2022
Test your code without writing mocks with ephemeral Docker containers ๐Ÿ“ฆ Setup popular services with just a couple lines of code โฑ๏ธ No bash, no yaml, only code ๐Ÿ’ป

Gnomock โ€“ tests without mocks ??๏ธ Spin up entire dependency stack ?? Setup initial dependency state โ€“ easily! ?? Test against actual, close to product

Dec 29, 2022
Record and replay your HTTP interactions for fast, deterministic and accurate tests

go-vcr go-vcr simplifies testing by recording your HTTP interactions and replaying them in future runs in order to provide fast, deterministic and acc

Dec 25, 2022
A Go library help testing your RESTful API application

RESTit A Go micro-framework to help writing RESTful API integration test Package RESTit provides helps to those who want to write an integration test

Oct 28, 2022
Automatically update your Go tests

autogold - automatically update your Go tests autogold makes go test -update automatically update your Go tests (golden files and Go values in e.g. fo

Dec 25, 2022
A next-generation testing tool. Orion provides a powerful DSL to write and automate your acceptance tests

Orion is born to change the way we implement our acceptance tests. It takes advantage of HCL from Hashicorp t o provide a simple DSL to write the acceptance tests.

Aug 31, 2022