Test Larky locally

To try it out today, email us at [email protected]

How to run Larky code locally

This page demonstrates how to run Larky code locally on your PC without any interactions with VGS Vault. There is no need to prepare any YAML or to send the HTTPS request. This technique is good to be used when, for example, you have working Python code that does the encryption of the message body and you need to rewrite it to Larky. While rewriting, it gives a possibility of seeing the results of Larky encryption and comparing them with Python results.

In order to run Larky tests, you will need to have Java with JDK 16 (this guide will have you use jenv for this), you will need to install Maven, you will create a GitHub access token, and you will need to download the repo. Once the repo is downloaded, you will use JDK 16 (in the jenv) to run the Maven commands to compile, build, and run the tests.

Prepare your environment

  1. Install jenv:

brew install jenv
echo 'export PATH="$HOME/.jenv/bin:$PATH"' >> ~/.zshrc
echo 'eval "$(jenv init -)"' >> ~/.zshrc
exec $SHELL -l
  1. Next, we use brew to search for openjdk16, install it, and add it to our jenv so that it has access to the right version of Java:

brew tap AdoptOpenJDK/openjdk
brew search /adoptopenjdk/
    ==> Casks
    adoptopenjdk-jre                      adoptopenjdk12                        adoptopenjdk14                        adoptopenjdk16 
    adoptopenjdk-openj9                   adoptopenjdk12-jre                    adoptopenjdk14-jre                    adoptopenjdk16-jre
    adoptopenjdk-openj9-jre               adoptopenjdk12-openj9                 adoptopenjdk14-openj9                 adoptopenjdk16-openj9
    adoptopenjdk-openj9-jre-large         adoptopenjdk12-openj9-jre             adoptopenjdk14-openj9-jre             adoptopenjdk16-openj9-jre
    adoptopenjdk-openj9-large             adoptopenjdk12-openj9-jre-large       adoptopenjdk14-openj9-jre-large       adoptopenjdk8 
    adoptopenjdk10                        adoptopenjdk12-openj9-large           adoptopenjdk14-openj9-large           adoptopenjdk8-jre
    adoptopenjdk11                       adoptopenjdk13                        adoptopenjdk15                        adoptopenjdk8-openj9
    adoptopenjdk11-jre                    adoptopenjdk13-jre                    adoptopenjdk15-jre                    adoptopenjdk8-openj9-jre
    adoptopenjdk11-openj9                 adoptopenjdk13-openj9                 adoptopenjdk15-openj9                 adoptopenjdk8-openj9-jre-large
    adoptopenjdk11-openj9-jre             adoptopenjdk13-openj9-jre             adoptopenjdk15-openj9-jre             adoptopenjdk8-openj9-large
    adoptopenjdk11-openj9-jre-large       adoptopenjdk13-openj9-jre-large       adoptopenjdk15-openj9-jre-large       adoptopenjdk9
    adoptopenjdk11-openj9-large           adoptopenjdk13-openj9-large           adoptopenjdk15-openj9-large           adoptopenjdk
brew install --cask adoptopenjdk16
jenv add /Library/Java/JavaVirtualMachines/adoptopenjdk-16.jdk/Contents/Home
jenv enable-plugin export
exec $SHELL -l
  1. Install maven:

brew install maven
mkdir ~/.m2
touch ~/.m2/settings.xml
# You will need to copy the contents of the settings.xml 
# from GitHub to your repo, placing your credentials in it
  1. Clone the larky repo and build it with jenv maven:

git clone https://github.com/verygoodsecurity/starlarky.git 
cd starlarky
jenv exec mvn clean package install
  1. This is it! Now you're ready to run the Larky test:

vim larky/src/test/resources/quick_tests/test_<name>.star 
jenv exec mvn -Dtest='LarkyQuickTests*' -Dlarky.quick_test=test_<name>.star test -pl larky

Writing a Larky Test

Writing a Larky test is simple. There are a few things that each Larky Test file will need.

  1. Filename. Test files should be named test_<name>.star. The directory larky/src/test/resources contains the directories in which you place the test file. Testing functionality for things in the Larky stdlib belongs in the stdlib_tests folder, testing functionality for vendor libraries goes in the vendor_tests folder, and anything else you need to make a quick test for goes in the quick_tests folder.

  2. You will need to import the unittest and asserts libraries.

  3. You will need to create an actual function to test the functionality you want to test. This should be concluded with an assertion.

  4. You will need the _testsuite() and _runner. The names of the functions that you want to run to test must be placed into the test suite via _suite.addTest(unittest.FunctionTestCase(<function>). If you do not do this, and simply declare a function def test(): and later call it with test() at the end of the file, it will not run the actual test. This can cause it to appear as though the test is passing when in fact it is not being run, and makes false positives for passing when the test may not actually pass.

You can reference this basic “Hello, World!” Larky test as an example:

load("@stdlib//unittest", "unittest")
load("@vendor//asserts", "asserts")
def test():
    a = 'Hello, '
    b = 'World!'
    c = a + b 
    asserts.assert_that(c).is_equal_to('Hello, World!')
def _testsuite():
    _suite = unittest.TestSuite()
    _suite.addTest(unittest.FunctionTestCase(test))
    return _suite
_runner = unittest.TextTestRunner()
_runner.run(_testsuite())

Saving this in the quick_tests folder as test_hello.star you can run it with the following:

jenv exec mvn -Dtest='LarkyQuickTests*' -Dlarky.quick_test=test_hello.star test -pl larky

Real Larky test example

Below are the code blocks in Python and Larky, respectively:

import requests
import binascii
import base64
from Crypto.Hash import SHA512
from Crypto.Hash import HMAC
method = 'POST'
uri = '/api/v3/transaction/rmscloudsimulator/debit'
headers = { 'Content-Type': 'application/json; charset=utf-8',
            'Date': 'Wed, 22 Dec 2021 10:52:03 UTC',
            'Shared-Secret': '5Q3E4yvQzDHU7dM54R46wzaSPD7nnP'}
body = '{"merchantTransactionId":"2019-09-02-0004","amount":"9.99","currency":"EUR"}'
content_type = headers['Content-Type']
date = headers['Date']
secret = headers['Shared-Secret']
body_utf8 = bytes(body, encoding="utf-8")
h = SHA512.new()
h.update(body_utf8)
signature = h.hexdigest()
print('>>> Signature: ', signature)
hmac_input = "\n".join(
        [
            method,
            signature,
            content_type,
            date,
            uri
        ]
    )
hmac_input = bytes(hmac_input, encoding="utf-8")
secret = str.encode(secret)
hash_value = HMAC.new(secret, digestmod=SHA512)
hash_value.update(hmac_input)
result = base64.b64encode(hash_value.digest()).decode("utf-8")
print('>>> Result  :', result)
      
{String.raw`load("@stdlib//unittest", "unittest")
load("@vendor//asserts", "asserts")
load("@stdlib//json", "json")
load("@stdlib//base64", base64="base64")
load("@vendor//Crypto/Hash/HMAC", HMAC="HMAC")
load("@vendor//Crypto/Hash/SHA512", SHA512="SHA512")
def test():
    # body = json.loads(input.body())
    method = 'POST'
    uri = '/api/v3/transaction/rmscloudsimulator/debit'
    headers = {'Content-Type': 'application/json; charset=utf-8',
               'Date': 'Wed, 22 Dec 2021 10:52:03 UTC',
               'Shared-Secret': '5Q3E4yvQzDHU7dM54R46wzaSPD7nnP'}
    body = '{"merchantTransactionId":"2019-09-02-0004","amount":"9.99","currency":"EUR"}'
    content_type = headers['Content-Type']
    date = headers['Date']
    secret = headers['Shared-Secret']
    body_utf8 = bytes(body, encoding="utf-8")
    signature = SHA512.new(body_utf8).hexdigest()
    print('\n\n >>> Signature: ', signature, '\n')
    hmac_input = "\n".join(
        [
            method,
            signature,
            content_type,
            date,
            uri
        ]
    )
    hmac_input = bytes(hmac_input, encoding="utf-8")
    secret = bytes(secret, encoding="utf-8")
    hash_value = HMAC.new(secret, hmac_input, digestmod=SHA512).digest()
    result = base64.b64encode(hash_value)
    print('\n\n >>> hexOutput: ', result, '\n')
def _testsuite():
    _suite = unittest.TestSuite()
    _suite.addTest(unittest.FunctionTestCase(test))
    return _suite
_runner = unittest.TextTestRunner()
_runner.run(_testsuite())
      `}

Execution results:

python sample.py
>>> Signature:  efe0b7cd39d6904dc90924b1a89629b14f11082ed2178cff562364ca0172318e1535bb8766fbe66e8cc44d311eba806349bfe185607eca12d9d0f377a03ee617
>>> Result  : HFgpaSQVGq9o7WQyQGAevpMtdVsBsw79aZOE2V9VqVUdaziNB+NegwSLk4gqBviOHB7drQYYP3HnS3zDyd9IWg==
jenv exec mvn -Dtest='LarkyQuickTests*' -Dlarky.quick_test=test_sha512_HMAC.star test -pl larky
...
0408 14:09:01.932 INFO: /Users/oneshot/Downloads/starlarky/larky/src/test/resources/quick_tests/test_sha512_HMAC.star:28:10:
.
 >>> Signature:  efe0b7cd39d6904dc90924b1a89629b14f11082ed2178cff562364ca0172318e1535bb8766fbe66e8cc44d311eba806349bfe185607eca12d9d0f377a03ee617
.
0408 14:09:01.944 INFO: /Users/oneshot/Downloads/starlarky/larky/src/test/resources/quick_tests/test_sha512_HMAC.star:46:10:
.
 >>> hexOutput:  HFgpaSQVGq9o7WQyQGAevpMtdVsBsw79aZOE2V9VqVUdaziNB+NegwSLk4gqBviOHB7drQYYP3HnS3zDyd9IWg==
.
Running test suite (1 tests to run):
Testing test >>> SUCCESS
...

As you see both Signature and hexOutput coincide, which means both code samples work in the same way. The Larky code is ready to be placed into YAML.

Last updated