Skip to content

Commit a981614

Browse files
authored
Merge pull request #63 from algorandfoundation/fix/switch-eval
fix: add tests to ensure switch clauses are evaluated only when reached
2 parents 6376dc0 + 3ff22c2 commit a981614

File tree

5 files changed

+143
-29
lines changed

5 files changed

+143
-29
lines changed

package-lock.json

Lines changed: 5 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@
7272
},
7373
"dependencies": {
7474
"@algorandfoundation/algorand-typescript": "^1.0.0-beta.20",
75-
"@algorandfoundation/puya-ts": "^1.0.0-beta.30",
75+
"@algorandfoundation/puya-ts": "^1.0.0-beta.34",
7676
"elliptic": "^6.5.7",
7777
"js-sha256": "^0.11.0",
7878
"js-sha3": "^0.9.3",
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import type { bytes, uint64 } from '@algorandfoundation/algorand-typescript'
2+
import { assert, Bytes, Contract, GlobalState, Uint64 } from '@algorandfoundation/algorand-typescript'
3+
4+
export class DemoContract extends Contract {
5+
run() {
6+
assert(this.test_uint64(1) === 3)
7+
assert(this.test_uint64(2) === 3)
8+
assert(this.test_uint64(3) === 1)
9+
assert(this.test_uint64(4) === 3)
10+
11+
assert(this.test_break(1) === 11)
12+
assert(this.test_break(2) === 12)
13+
assert(this.test_break(3) === 10)
14+
assert(this.test_break(4) === 14)
15+
assert(this.test_break(5) === 50)
16+
17+
assert(this.test_bytes(Bytes('hmmm')))
18+
assert(this.test_bytes(Bytes.fromHex('ff')))
19+
assert(this.test_bytes(Bytes.fromBase64('ZHNmc2Rmc2Q=')))
20+
assert(this.test_bytes(Bytes.fromBase32('ONSGMZ3OMJTGOZDGMRSGM===')))
21+
assert(!this.test_bytes(Bytes()))
22+
}
23+
24+
private test_uint64(x: uint64): uint64 {
25+
switch (x) {
26+
case 1:
27+
case 2:
28+
case Uint64(4):
29+
return 3
30+
default: {
31+
return 1
32+
}
33+
}
34+
}
35+
36+
private test_break(x: uint64): uint64 {
37+
let i: uint64 = 10
38+
switch (x) {
39+
case 1:
40+
case 2:
41+
case Uint64(4):
42+
i += x
43+
break
44+
case 5:
45+
i *= x
46+
}
47+
return i
48+
}
49+
50+
private test_bytes(x: bytes): boolean {
51+
switch (x) {
52+
case Bytes('hmmm'):
53+
case Bytes.fromHex('Ff'):
54+
case Bytes.fromBase64('ZHNmc2Rmc2Q='):
55+
case Bytes.fromBase32('ONSGMZ3OMJTGOZDGMRSGM==='):
56+
return true
57+
}
58+
return false
59+
}
60+
61+
evalCount = GlobalState<uint64>()
62+
63+
private increaseEvalAndReturn(n: uint64) {
64+
this.evalCount.value++
65+
return n
66+
}
67+
68+
public test_side_effects(n: uint64) {
69+
this.evalCount.value = 0
70+
71+
switch (n) {
72+
case this.increaseEvalAndReturn(n - 1):
73+
break
74+
case this.increaseEvalAndReturn(n):
75+
break
76+
case this.increaseEvalAndReturn(n + 1):
77+
break
78+
}
79+
80+
assert(this.evalCount.value === 2, 'Only two functions should be evaluated')
81+
}
82+
83+
public test_non_trivial_termination_of_clause(n: uint64, y: uint64): uint64 {
84+
switch (n) {
85+
case 1:
86+
if (y % 2 === 0) {
87+
return y
88+
} else {
89+
return n
90+
}
91+
default:
92+
return y * n
93+
}
94+
}
95+
}

tests/switch-statements.spec.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { beforeAll, describe } from 'vitest'
2+
import { createArc4TestFixture } from './test-fixture'
3+
4+
describe('switch statements', () => {
5+
const [test, localnetFixture] = createArc4TestFixture('tests/artifacts/switch-statements/contract.algo.ts', { DemoContract: {} })
6+
beforeAll(async () => {
7+
await localnetFixture.newScope()
8+
})
9+
10+
test('runs', async ({ appClientDemoContract }) => {
11+
await appClientDemoContract.send.call({ method: 'run', args: [] })
12+
})
13+
14+
test('test_side_effects', async ({ appClientDemoContract }) => {
15+
await appClientDemoContract.send.call({ method: 'test_side_effects', args: [5] })
16+
})
17+
})

tests/test-fixture.ts

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import type { AppFactory, AppFactoryDeployParams } from '@algorandfoundation/alg
99
import type { AssetCreateParams } from '@algorandfoundation/algokit-utils/types/composer'
1010
import { nullLogger } from '@algorandfoundation/algokit-utils/types/logging'
1111
import type { AlgorandFixture } from '@algorandfoundation/algokit-utils/types/testing'
12-
import { compile, LoggingContext } from '@algorandfoundation/puya-ts'
12+
import { compile, CompileOptions, LoggingContext, processInputPaths } from '@algorandfoundation/puya-ts'
1313
import type { Use } from '@vitest/runner/types'
1414
import { OnApplicationComplete } from 'algosdk'
1515
import fs from 'fs'
@@ -237,32 +237,33 @@ async function compilePath(
237237
const logCtx = LoggingContext.create()
238238

239239
return await logCtx.run(async () => {
240-
await compile({
241-
outputAwstJson: false,
242-
outputAwst: false,
243-
paths: [path],
244-
outDir: tempDir.dirPath,
245-
dryRun: false,
246-
logLevel: 'error' as Parameters<typeof compile>[0]['logLevel'],
247-
skipVersionCheck: true,
240+
await compile(
241+
new CompileOptions({
242+
outputAwstJson: false,
243+
outputAwst: false,
244+
filePaths: processInputPaths({ paths: [path], outDir: tempDir.dirPath }),
245+
dryRun: false,
246+
logLevel: 'error' as Parameters<typeof compile>[0]['logLevel'],
247+
skipVersionCheck: true,
248248

249-
outputSsaIr: false,
250-
outputOptimizationIr: false,
251-
outputDestructuredIr: false,
252-
outputMemoryIr: false,
249+
outputSsaIr: false,
250+
outputOptimizationIr: false,
251+
outputDestructuredIr: false,
252+
outputMemoryIr: false,
253253

254-
debugLevel: 1,
255-
targetAvmVersion: 10,
256-
cliTemplateDefinitions: {},
257-
templateVarsPrefix: 'TMPL_',
258-
localsCoalescingStrategy: 'root_operand' as Parameters<typeof compile>[0]['localsCoalescingStrategy'],
254+
debugLevel: 1,
255+
targetAvmVersion: 10,
256+
cliTemplateDefinitions: {},
257+
templateVarsPrefix: 'TMPL_',
258+
localsCoalescingStrategy: 'root_operand' as Parameters<typeof compile>[0]['localsCoalescingStrategy'],
259259

260-
outputArc32: false,
261-
outputTeal: false,
262-
outputSourceMap: true,
263-
optimizationLevel: 0,
264-
...options,
265-
})
260+
outputArc32: false,
261+
outputTeal: false,
262+
outputSourceMap: true,
263+
optimizationLevel: 0,
264+
...options,
265+
}),
266+
)
266267
for (const log of logCtx.logEvents) {
267268
switch (log.level) {
268269
case 'error':

0 commit comments

Comments
 (0)