Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "async" API's for Odyssey to test out. #966

Closed
wants to merge 8 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 97 additions & 9 deletions infra/testApi.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,78 @@ import { strict as assert } from 'node:assert'; // use strict equality everywhe
// Future TODO: before this API becomes set in stone/offered publicly, we should change the results of these methods to be just the output data rather than duplicating input values.

// Reusable testing data
const SAMPLE_SIZE = 8000
const FPCoreFormula = '(FPCore (x) (- (sqrt (+ x 1)) (sqrt x)))'
const FPCoreFormula2 = '(FPCore (x) (- (sqrt (+ x 1))))'
const eval_sample = [[[1], -1.4142135623730951]]
const analyzeBody = JSON.stringify({
formula: FPCoreFormula, sample: [[[
14.97651307489794
], 0.12711304680349078]]
})
const alternativesBody = JSON.stringify({
formula: FPCoreFormula, sample: [[[
14.97651307489794
], 0.12711304680349078]]
})
const exactsBody = JSON.stringify({
formula: FPCoreFormula2, sample: eval_sample
})
const calculateBody = JSON.stringify({
formula: FPCoreFormula2, sample: eval_sample
})
const costBody = JSON.stringify({
formula: FPCoreFormula2, sample: eval_sample
})
// --------------------------------------
// TEST ASYNC APIs
// --------------------------------------

// Sample
const sampleStartData = await testAsyncAPI("sample-start", JSON.stringify({ formula: FPCoreFormula2, seed: 5 }))
Copy link
Contributor

@pavpanchekha pavpanchekha Aug 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this body not stored in a variable above like all the others?

assert.ok(sampleStartData.points)
assert.equal(sampleStartData.points.length, SAMPLE_SIZE, `sample size should be ${SAMPLE_SIZE}`)

// Analyze
const analyzeStartData = await testAsyncAPI("analyze-start", analyzeBody)
assertIdAndPath(analyzeStartData)
assert.deepEqual(analyzeStartData.points, [[[14.97651307489794], "2.3"]])

// Localerror
const localErrorBody = JSON.stringify({
formula: FPCoreFormula, sample: sampleStartData.points
})
const localerrorStartData = await testAsyncAPI("localerror-start", analyzeBody)
assertIdAndPath(localerrorStartData)
assert.equal(localerrorStartData.tree['avg-error'] > 0, true)

// Alternatives
const alternativesStartData = await testAsyncAPI("alternatives-start", alternativesBody)
assertIdAndPath(alternativesStartData)
assert.equal(Array.isArray(alternativesStartData.alternatives), true)

// Exacts endpoint
const exactsStartData = await testAsyncAPI("exacts-start", exactsBody)
assertIdAndPath(exactsStartData)
assert.deepEqual(exactsStartData.points, [[[1], -1.4142135623730951]])

// Calculate endpoint
const calculateStartData = await testAsyncAPI("calculate-start", calculateBody)
assertIdAndPath(calculateStartData)
assert.deepEqual(calculateStartData.points, [[[1], -1.4142135623730951]])

// Cost endpoint
const costStartData = await testAsyncAPI("cost-start", costBody)
assertIdAndPath(costStartData)
assert.equal(costStartData.cost > 0, true)

const explainStartData = await testAsyncAPI("explanations-start",localErrorBody)
assertIdAndPath(explainStartData)
assert.equal(explainStartData.explanation.length > 0, true, 'explanation should not be empty');

// --------------------------------------
// END ASYNC APIS
// --------------------------------------

// improve endpoint
const improveResponse = await callHerbie(`/improve?formula=${encodeURIComponent(FPCoreFormula2)}`, { method: 'GET' })
Expand Down Expand Up @@ -53,7 +122,6 @@ assert.notEqual(jid, null)
const sample = await sampleRSP.json()
assertIdAndPath(sample)

const SAMPLE_SIZE = 8000
assert.ok(sample.points)
const points = sample.points
assert.equal(points.length, SAMPLE_SIZE, `sample size should be ${SAMPLE_SIZE}`)
Expand All @@ -68,20 +136,14 @@ assert.deepEqual(points[1], points2[1])

// Analyze endpoint
const errors = await callHerbie("/api/analyze", {
method: 'POST', body: JSON.stringify({
formula: FPCoreFormula, sample: [[[
14.97651307489794
], 0.12711304680349078]]
})
method: 'POST', body: analyzeBody
})
assertIdAndPath(errors)
assert.deepEqual(errors.points, [[[14.97651307489794], "2.3"]])

// Local error endpoint
const localError = await callHerbie("/api/localerror", {
method: 'POST', body: JSON.stringify({
formula: FPCoreFormula, sample: sample2.points
})
method: 'POST', body: localErrorBody
})
assertIdAndPath(localError)
assert.equal(localError.tree['avg-error'] > 0, true)
Expand Down Expand Up @@ -217,5 +279,31 @@ async function callHerbie(endPoint, body) {

function assertIdAndPath(json) {
assert.equal(json.job.length > 0, true)
// TODO regex for valid hashes?
assert.equal(json.path.includes("."), true)
}

async function testAsyncAPI(endpointName, fetchBodyString) {
const serverURL = `http://127.0.0.1:8000`
const sampleStartURL = new URL(`${serverURL}${`/api/${endpointName}`}`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const sampleStartURL = new URL(`${serverURL}${`/api/${endpointName}`}`)
const sampleStartURL = new URL(`${serverURL}/api/${endpointName}`)


const rsp = await fetch(sampleStartURL, { method: 'POST', body: fetchBodyString })
assert.equal(rsp.status, 200)
const rspJSON = await rsp.json()
assertIdAndPath(rspJSON)

const checkStatusURL = new URL(`${serverURL}${"/check-status/"}${rspJSON.job}`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const checkStatusURL = new URL(`${serverURL}${"/check-status/"}${rspJSON.job}`)
const checkStatusURL = new URL(`${serverURL}/check-status/${rspJSON.job}`)

let counter = 0
let cap = 100
let checkStatus = await fetch(checkStatusURL, { method: 'GET' })
// Loop to wait for for job to finnish
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Loop to wait for for job to finnish
// Loop to wait for for job to finish

Big fan of the Finns but not like this.

while (checkStatus.status != 201 && counter < cap) {
counter += 1
checkStatus = await fetch(checkStatusURL, { method: 'GET' })
await new Promise(r => setTimeout(r, 100)); // ms
}
assert.equal(checkStatus.statusText, 'Job complete')
const resultsURL = new URL(`${serverURL}${"/api/results/"}${rspJSON.job}`)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const resultsURL = new URL(`${serverURL}${"/api/results/"}${rspJSON.job}`)
const resultsURL = new URL(`${serverURL}/api/results/${rspJSON.job}`)

const results = await fetch(resultsURL, { method: 'GET' })
return await results.json()
}
163 changes: 163 additions & 0 deletions src/api/demo.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,25 @@
[("check-status" (string-arg)) check-status]
[("timeline" (string-arg)) get-timeline]
[("up") check-up]
[("api" "results" (string-arg)) get-results]
[("api" "sample") #:method "post" sample-endpoint]
[("api" "sample-start") #:method "post" sample-start-endpoint]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, these are all fine, it's not the end of the world or anything, but I think it would be a lot nicer to do /api/start/foo instead of /api/foo-start. We already have a separator character!

[("api" "analyze") #:method "post" analyze-endpoint]
[("api" "analyze-start") #:method "post" analyze-start-endpoint]
[("api" "localerror") #:method "post" local-error-endpoint]
[("api" "localerror-start") #:method "post" local-error-start-endpoint]
[("api" "alternatives") #:method "post" alternatives-endpoint]
[("api" "alternatives-start") #:method "post" alternatives-start-endpoint]
[("api" "exacts") #:method "post" exacts-endpoint]
[("api" "exacts-start") #:method "post" exacts-start-endpoint]
[("api" "calculate") #:method "post" calculate-endpoint]
[("api" "calculate-start") #:method "post" calculate-start-endpoint]
[("api" "cost") #:method "post" cost-endpoint]
[("api" "cost-start") #:method "post" cost-start-endpoint]
[("api" "mathjs") #:method "post" ->mathjs-endpoint]
[("api" "translate") #:method "post" translate-endpoint]
[("api" "explanations") #:method "post" explanations-endpoint]
[("api" "explanations-start") #:method "post" explanations-start-endpoint]
[((hash-arg) (string-arg)) generate-page]
[("results.json") generate-report]))

Expand Down Expand Up @@ -360,6 +369,27 @@
(header #"Access-Control-Allow-Origin" (string->bytes/utf-8 "*")))
(λ (out) (write-json (hash-ref job-result 'timeline) out)))]))

(define (get-results req job-id)
(match (get-results-for job-id)
[#f
(response 404
#"Job Not Found"
(current-seconds)
#"text/plain"
(list (header #"X-Job-Count" (string->bytes/utf-8 (~a (job-count))))
(header #"X-Herbie-Job-ID" (string->bytes/utf-8 job-id))
(header #"Access-Control-Allow-Origin" (string->bytes/utf-8 "*")))
(λ (out) `()))]
[job-result
(response 201
#"Job complete"
(current-seconds)
#"text/plain"
(list (header #"X-Job-Count" (string->bytes/utf-8 (~a (job-count))))
(header #"X-Herbie-Job-ID" (string->bytes/utf-8 job-id))
(header #"Access-Control-Allow-Origin" (string->bytes/utf-8 "*")))
(λ (out) (write-json job-result out)))]))

; /api/sample endpoint: test in console on demo page:
;; (await fetch('/api/sample', {method: 'POST', body: JSON.stringify({formula: "(FPCore (x) (- (sqrt (+ x 1))))", seed: 5})})).json()
(define sample-endpoint
Expand All @@ -374,6 +404,20 @@
(define id (start-job command))
(wait-for-job id))))

; /api/sample endpoint: test in console on demo page:
;; (await fetch('/api/sample', {method: 'POST', body: JSON.stringify({formula: "(FPCore (x) (- (sqrt (+ x 1))))", seed: 5})})).json()
(define sample-start-endpoint
(post-with-json-response
(lambda (post-data)
(define formula-str (hash-ref post-data 'formula))
(define formula (read-syntax 'web (open-input-string formula-str)))
(define seed* (hash-ref post-data 'seed))
(define test (parse-test formula))
(define command
(create-job 'sample test #:seed seed* #:pcontext #f #:profile? #f #:timeline-disabled? #t))
(define job-id (start-job command))
(hasheq 'job job-id 'path (make-path job-id)))))

(define explanations-endpoint
(post-with-json-response (lambda (post-data)
(define formula-str (hash-ref post-data 'formula))
Expand All @@ -392,6 +436,24 @@
(define id (start-job command))
(wait-for-job id))))

(define explanations-start-endpoint
(post-with-json-response (lambda (post-data)
(define formula-str (hash-ref post-data 'formula))
(define formula (read-syntax 'web (open-input-string formula-str)))
(define sample (hash-ref post-data 'sample))
(define seed (hash-ref post-data 'seed #f))
(define test (parse-test formula))
(define pcontext (json->pcontext sample (test-context test)))
(define command
(create-job 'explanations
test
#:seed seed
#:pcontext pcontext
#:profile? #f
#:timeline-disabled? #t))
(define job-id (start-job command))
(hasheq 'job job-id 'path (make-path job-id)))))

(define analyze-endpoint
(post-with-json-response (lambda (post-data)
(define formula-str (hash-ref post-data 'formula))
Expand All @@ -410,6 +472,24 @@
(define id (start-job command))
(wait-for-job id))))

(define analyze-start-endpoint
(post-with-json-response (lambda (post-data)
(define formula-str (hash-ref post-data 'formula))
(define formula (read-syntax 'web (open-input-string formula-str)))
(define sample (hash-ref post-data 'sample))
(define seed (hash-ref post-data 'seed #f))
(define test (parse-test formula))
(define pcontext (json->pcontext sample (test-context test)))
(define command
(create-job 'errors
test
#:seed seed
#:pcontext pcontext
#:profile? #f
#:timeline-disabled? #t))
(define job-id (start-job command))
(hasheq 'job job-id 'path (make-path job-id)))))
Comment on lines +475 to +491
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could be convinced to merge this but surely this is extremely duplicative with the existing non-async endpoints? Could we have a single function and then generic wrappers around it to make it sync or async?


;; (await fetch('/api/exacts', {method: 'POST', body: JSON.stringify({formula: "(FPCore (x) (- (sqrt (+ x 1))))", points: [[1, 1]]})})).json()
(define exacts-endpoint
(post-with-json-response (lambda (post-data)
Expand All @@ -429,6 +509,24 @@
(define id (start-job command))
(wait-for-job id))))

(define exacts-start-endpoint
(post-with-json-response (lambda (post-data)
(define formula
(read-syntax 'web (open-input-string (hash-ref post-data 'formula))))
(define sample (hash-ref post-data 'sample))
(define seed (hash-ref post-data 'seed #f))
(define test (parse-test formula))
(define pcontext (json->pcontext sample (test-context test)))
(define command
(create-job 'exacts
test
#:seed seed
#:pcontext pcontext
#:profile? #f
#:timeline-disabled? #t))
(define job-id (start-job command))
(hasheq 'job job-id 'path (make-path job-id)))))

(define calculate-endpoint
(post-with-json-response (lambda (post-data)
(define formula
Expand All @@ -447,6 +545,24 @@
(define id (start-job command))
(wait-for-job id))))

(define calculate-start-endpoint
(post-with-json-response (lambda (post-data)
(define formula
(read-syntax 'web (open-input-string (hash-ref post-data 'formula))))
(define sample (hash-ref post-data 'sample))
(define seed (hash-ref post-data 'seed #f))
(define test (parse-test formula))
(define pcontext (json->pcontext sample (test-context test)))
(define command
(create-job 'evaluate
test
#:seed seed
#:pcontext pcontext
#:profile? #f
#:timeline-disabled? #t))
(define job-id (start-job command))
(hasheq 'job job-id 'path (make-path job-id)))))

(define local-error-endpoint
(post-with-json-response (lambda (post-data)
(define formula
Expand All @@ -466,6 +582,25 @@
(define id (start-job command))
(wait-for-job id))))

(define local-error-start-endpoint
(post-with-json-response (lambda (post-data)
(define formula
(read-syntax 'web (open-input-string (hash-ref post-data 'formula))))
(define sample (hash-ref post-data 'sample))
(define seed (hash-ref post-data 'seed #f))
(define test (parse-test formula))
(define expr (prog->fpcore (test-input test)))
(define pcontext (json->pcontext sample (test-context test)))
(define command
(create-job 'local-error
test
#:seed seed
#:pcontext pcontext
#:profile? #f
#:timeline-disabled? #t))
(define job-id (start-job command))
(hasheq 'job job-id 'path (make-path job-id)))))

(define alternatives-endpoint
(post-with-json-response (lambda (post-data)
(define formula
Expand All @@ -484,6 +619,24 @@
(define id (start-job command))
(wait-for-job id))))

(define alternatives-start-endpoint
(post-with-json-response (lambda (post-data)
(define formula
(read-syntax 'web (open-input-string (hash-ref post-data 'formula))))
(define sample (hash-ref post-data 'sample))
(define seed (hash-ref post-data 'seed #f))
(define test (parse-test formula))
(define pcontext (json->pcontext sample (test-context test)))
(define command
(create-job 'alternatives
test
#:seed seed
#:pcontext pcontext
#:profile? #f
#:timeline-disabled? #t))
(define job-id (start-job command))
(hasheq 'job job-id 'path (make-path job-id)))))

(define ->mathjs-endpoint
(post-with-json-response (lambda (post-data)
(define formula
Expand All @@ -503,6 +656,16 @@
(define id (start-job command))
(wait-for-job id))))

(define cost-start-endpoint
(post-with-json-response
(lambda (post-data)
(define formula (read-syntax 'web (open-input-string (hash-ref post-data 'formula))))
(define test (parse-test formula))
(define command
(create-job 'cost test #:seed #f #:pcontext #f #:profile? #f #:timeline-disabled? #f))
(define job-id (start-job command))
(hasheq 'job job-id 'path (make-path job-id)))))

(define translate-endpoint
(post-with-json-response (lambda (post-data)
; FPCore formula and target language
Expand Down
Loading