07 Jun 2024
I made a thing!
Okay, it’s nothing big, but it was a nice, quick turnaround, one feature product.
I have a friend who sells a few of his own products in local markets, and he had a bit of a problem.
He wants to accept cashless payments, but he has a revenue of a kid’s lemonade stand, and he does it more as a hobby, so he doesn’t want to pay any 3rd party fees.
Since almost everyone around has a mobile banking app with QR code instant payment,
I of course immediately suggested “hey just generate QR code in your banking mobile app?”
you will immediately see if you received the payment. But this has a few problems:
- his mobile banking app is clunky, the QR creation dialog is hidden behind 4 clicks
- you need to do a FaceID check every time
- you can’t let 3rd party, like a family member, handle it, since you automatically grant them complete access to your bank account
MVP idea
What if we extracted the QR code generation to a standalone app, that would not have access to your bank account?
You could have it open in a tablet, with your mobile banking installed there, so you could see the instant payment notification, so even 3rd party users could confirm the payment was made.
Few moments later:
QR Card
mobile first, extremely simple UI, saving state to query param, works offline.
Implementation
Notes
There are already some existing web apps for qr generation, but they are not very mobile friendly, and also usually make some api calls.
I don’t think this should be used for anything serious.
Only for CZK to keep the UI simple.
Open source https://github.com/cvakiitho/qr-card
No Analytics, actually no external http calls.
03 Aug 2020
Lately I discovered how to simulate REPL, and at least partially debug real Jenkins pipeline job,
I’m not sure if I missed this in official docs, or if it’s not there, but I believe this is really useful
especially with larger pipeline project with enough shared libraries to make your head spin.
Writing pipelines is imo hard for a few reasons. First is, documentation is really not great, you are forced to use step generator, and it’s (?) icon quite a bit.
Second is, and a major one, the “compile time” for a simplest pipeline is at least 10s. (And it takes at least 1000 compilations before you have a good program citation missing)
git add -u; git commit --amend --no-edit
git push origin master -f
Click Build
Click Dot to see console output
You could make it faster by using something like https://marketplace.visualstudio.com/items?itemName=dave-hagedorn.jenkins-runner
But good luck with auth, and with servers you aren’t admin.
Third one for me until last week was, you can’t really debug them, which together with 2. was a real pain.
Last week I discovered you can use while
, input, and Groovy Eval to simulate “REPL” with something like this:
while (true) {
def cmd = input message: 'What to run:', parameters: [string(defaultValue: '', description: '', name: 'cmd')]
try {
print "Running ${cmd}"
print Eval.xy(this, script, cmd)
} catch (e) {
print e
}
}
When you pass this to jenkins pipeline, it acts as a breakpoint in code, and you can pass groovy to the Input requested
in console output.
I like to use this together with Groovy dump Object method, to see what is inside this
, script
inside jenkins shared libraries mostly. Together with simple Groovy scratch file to visualize larger dumps a bit better:
Pasting x.binding.dump()
to the input creates large single line string <groovy.lang.Binding@1f7266a2 variables=[steps:org.jenkinsci.plugins.workflow.cps.DSL@3eea148c, pipelineConfig:[general:[stageStatus:[success:SUCCESS, skipped:SKIPPED, failure:FAILURE, unstable:UNSTABLE, notrun:NOT_RUN, aborted:ABORTED]], maven:[mvnTest:[mvnGoal:clean verify, mvnProfiles:[execute.qunit, debug.test.build], mvnOptions:[-f infra/pom.xml],...
given to this naive scratch script:
String dump = '''<groovy.lang.Binding....>
'''
def level = 1
def result = ''
for (def x in dump) {
if (x =="[") {
result += '\n'
result = result + (' ' * level)
level++
}
if (x == "]"){
level--
}
result += x
}
print result
produces something like this:
<groovy.lang.Binding@1f7266a2 variables=
[steps:org.jenkinsci.plugins.workflow.cps.DSL@3eea148c, pipelineConfig:
[general:
[stageStatus:
[success:SUCCESS, skipped:SKIPPED, failure:FAILURE, unstable:UNSTABLE, notrun:NOT_RUN, aborted:ABORTED]], maven:
[mvnTest:
[mvnGoal:clean verify, mvnProfiles:
[execute.qunit, debug.test.build], mvnOptions:
[-f infra/pom.xml], mvnSystemProperties:
[mvnGoal:clean deploy, mvnOptions:
[-f infra/pom.xml], mvnSystemProperties:
[mvnGoal:clean verify, mvnOptions:
[-f infra/pom.xml], mvnProfiles:
[fiori.eslint.build, analysis.plugin]], mvnVerifyNoFailure:
...
Notes
- depending on you setup you can get ENV variable with
x.env.dump()
- it is possible to call pipeline steps with
x.binding.steps.<stepName>("<step params>")
.
11:40:44 Running x.binding.steps.sh('date')
[Pipeline] sh
11:40:44 + date
11:40:44 Mon 03 Aug 2020 11:40:44 AM CEST
One problem with Groovy Eval
class is, that it doesn’t expose whole stack, for security reasons, so you can inspect only two variables in the breakpoint input loop, usually this
, and script
.
All variables you exposed via eval have binding which has steps
It is possible to do a full thread dump on jenkins, and deserialize it’s state via <jobName>/lastBuild/threadDump/
and lastBuild/threadDump/program.xml
, but the enormous xml is hard to navigate, and read. It’s ok for searching.
Sources:
16 Apr 2020
In todays post I’d like to go through Jenkins pipeline unit testing using jenkins-spock library, it’s written from groovy newbie viewpoint, and mostly using examples to show how to do things. I left some parts as direct quotes, and hopefully didn’t forget any sources, enjoy.
Post has two parts, Spock with just enough spock explanation to write tests, and Jenkins-Spock with jenkins-spock library quick tour.
Jenkins-spock uses standard groovy spock for test structure:
You need to extend Specification
class if you are testing groovy classes, or JenkinsPipelineSpecification
if you are testing JenkinsFiles, and vars scripts.
That is fairly standard, and similar to JUnit,
Tests themselves live inside feature methods which are named using strings.
And are broken into parts via blocks.
Simple test:
// test suite
class MyFirstTest extends JenkinsPipelineSpecification {
def "Test Name"(): // test definition
expect: // block
1 == 1 // implicit assertion
}
Blocks:
Spock supports BDD style testing via blocks out of the box. 6 blocks are available:
given, when, then, expect, cleanup, where
given
: setup phase of a test, also everything before any block is implicitly moved in given block. equals setup:
when
: do something with the system under test
then
: test the response - implicitly assertions
expect
: do something and test response - if you don’t like given, when, then - implicit assertions
cleanup
: cleanup phase
where
: data driven testing
Extending our example:
class MyFirstTest extends JenkinsPipelineSpecification {
def "Test Name"(): // test definition
given:
int left = 1
int right = 1
when:
int results = left + right
expect:
results == 2 // implicit assertion
}
all boolean expressions inside expect and then blocks are asserted, you can use groovys assert
keyword to check expressions anywhere else.
You can even add documentation to your test, by adding string after the block, and use and
to make it more readable:
given: "open a database connection"
// code goes here
and: "seed the customer table"
// code goes here
and: "seed the product table"
// code goes here
Spec setup methods:
To surprise nobody, there are methods to run before each feature method, once per spec, and same for cleanup:
def setupSpec() {} // runs once - before the first feature method
def setup() {} // runs before every feature method
def cleanup() {} // runs after every feature method
def cleanupSpec() {} // runs once - after the last feature method
Data Driven testing:
If you need to test more conditions at once, use where:
block,several syntactic ways to use it, my favorite is data table:
class MathSpec extends Specification {
def "maximum of two numbers"(int a, int b, int c) {
expect:
Math.max(a, b) == c
where:
a | b | c
1 | 3 | 3
7 | 4 | 7
0 | 0 | 0
}
}
more here: http://spockframework.org/spock/docs/1.3/data_driven_testing.html
Cardinality:
Spock supports cardinality testing with <int> *
syntax, number represents number of calls to given method:
1 * MyFunction.method("hello") // exactly one call
0 * MyFunction.method("hello") // zero calls
http://spockframework.org/spock/docs/1.3/interaction_based_testing.html#_cardinality
Mocking:
Spock has it’s own mocking framework, making use of interesting concepts brought to the JVM by Groovy. First, let’s instantiate a Mock:
PaymentGateway paymentGateway = Mock()
In this case, the type of our mock is inferred by the variable type. As Groovy is a dynamic language, we can also provide a type argument, allow us to not have to assign our mock to any particular type:
def paymentGateway = Mock(PaymentGateway)
Now, whenever we call a method on our PaymentGateway mock, a default response will be given, without a real instance being invoked:
when:
def result = paymentGateway.makePayment(12.99)
then:
result == false
The term for this is lenient mocking. This means that mock methods which have not been defined will return sensible defaults, as opposed to throwing an exception. This is by design in Spock, in order to make mocks and thus tests less brittle.
https://www.baeldung.com/groovy-spock#2-mocking-using-spock
Stubbing:
We can also configure methods called on our mock to respond in a certain way to different arguments. Let’s try getting our PaymentGateway mock to return true when we make a payment of 20:
given:
paymentGateway.makePayment(20) >> true
when:
def result = paymentGateway.makePayment(20)
then:
result == true
What’s interesting here, is how Spock makes use of Groovy’s operator overloading in order to stub method calls. With Java, we have to call real methods, which arguably means that the resulting code is more verbose and potentially less expressive.
Now, let’s try a few more types of stubbing.
If we stopped caring about our method argument and always wanted to return true, we could just use an underscore:
paymentGateway.makePayment(_) >> true
If we wanted to alternate between different responses, we could provide a list, for which each element will be returned in sequence:
paymentGateway.makePayment(_) >>> [true, true, false, true]
There are more possibilities, and these may be covered in a more advanced future article on mocking.
https://www.baeldung.com/groovy-spock#3-stubbing-method-calls-on-mocks
JUnit vs Spock:
Although Spock uses a different terminology, many of its concepts and features are inspired by JUnit. Here is a rough comparison:
Spock |
JUnit |
Specification |
Test class |
setup() |
@Before |
cleanup() |
@After |
setupSpec() |
@BeforeClass |
cleanupSpec() |
@AfterClass |
Feature |
Test |
Feature method |
Test method |
Data-driven feature |
Theory |
Condition |
Assertion |
Exception condition |
@Test(expected=...) |
Interaction |
Mock expectation (e.g. in Mockito) |
Now we know how to write basic spock tests, lets move to jenkins part.
A tiny bit about Jenkins pipelines,
Jenkins pipelines comes in 3 structures:
- classes
- pipeline variables (vars/something.groovy)
- pipeline scripts (whole Jenkinsfiles)
Classes are standard groovy classes, and are testable without anything special using standard spock unit tests.
The other too are a bit different, and have global variables that are coming from Jenkins, that needs to be mocked.
Those global variables, are essentially of 3 types:
In order to run jenkins pipeline code without Jenkins, we need to mock every global variable so we don’t get undefined reference compilation errors.
All the mocking is done by JenkinsPipelineSpecification
class, and we need to extend it to write tests.
This class ensures that all pipeline extension points exist as Spock Mock objects so that the calls will succeed and that interactions can be inspected, stubbed, and verified. You can access a Spock mock for any pipeline step that would exist by using getPipelineMock("object")
.
Mock Pipeline Steps
Mock pipeline steps are available at getPipelineMock("stepName")
. You can verify interactions with them and stub them:
then:
1 * getPipelineMock("echo")("hello world") // check that echo was called with hello world once
//stubs sh call when called with echo hi, to return hi
1 * getPipelineMock("sh")( [returnStdout: true, script: "echo hi"] ) >> "hi"
For example, the node(…) { … } step’s body is automatically executed:
when:
node('some-label') {
echo( "hello" )
}
then:
1 * getPipelineMock("node")("some-label") // test that node was called with 'some-label`
1 * getPipelineMock("echo")("hello") // test that echo was called with 'hello'
Pipeline Vars:
Jenkins pipeline scripts need a special treatment, because they contain global variables provided by Jenkins Plugin, Jenkins itself, and potentially implicitly loaded shared libraries. Because of that we need to mock them, and stub them.
loadPipelineScriptForTest()
:
loads our script for testing, and enables us to run them with arguments:
def MyFunction = loadPipelineScriptForTest("vars/MyFunction.groovy")
MyFunction('test arg')
If we have some Jenkins global env var in the script, we need to set it to something:
MyFunction.getBinding().setVariable( "BRANCH_NAME", "master" )
Method calls on GlobalVariables are available as mocks at getPipelineMock("VariableName.methodName")
Stubbing pipeline vars:
stubbing is done like for all mocks, just stub .call method:
given:
//when MyFunction gets called with Hello, return Hello World.
getPipelineMock("MyFunction.call")("Hello") >> "Hello World"
when:
//run your script with MyFunction call:
Jenkinsfile.run()
then:
// echo was called once with Hello World
1 * getPipelineMock("echo")("Hello World")
Pipeline Scripts:
You can also test whole pipeline JenkinsFiles, only difference is you have to call .run()
on them, after loaded from loadPipelineScriptForTest()
def "Jenkinsfile"() {
setup:
def Jenkinsfile = loadPipelineScriptForTest("com/homeaway/CoolJenkinsfile.groovy")
when:
Jenkinsfile.run()
then:
1 * getPipelineMock("node")("legacy", _)
1 * getPipelineMock("echo")("hello world")
}
Explicit mocks:
If for some reason you won’t get automatic mock for a variable, for example from a plugin you don’t have in a dependencies, you can explicitly mock them by: explicitlyMockPipelineStep("varName")
This is about it for unit testing jenkins pipelines with jenkins-spock, I personally quite enjoy the implicit mocks for almost everything, and BDD variant of Spock. All of it seems a lot more lightweight then using JenkinsPipelineUnit.
Sources:
- http://spockframework.org/spock/docs/1.3/spock_primer.html
- https://javadoc.io/static/com.homeaway.devtools.jenkins/jenkins-spock/2.1.2/com/homeaway/devtools/jenkins/testing/JenkinsPipelineSpecification.htm
- https://www.baeldung.com/groovy-spock
- https://github.com/ExpediaGroup/jenkins-spock