Skip to content

Commit

Permalink
bidsTests just started
Browse files Browse the repository at this point in the history
  • Loading branch information
VisLab committed Oct 23, 2024
1 parent cd53a2c commit f6034df
Show file tree
Hide file tree
Showing 9 changed files with 442 additions and 16 deletions.
33 changes: 20 additions & 13 deletions bids/validator/bidsHedTsvValidator.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ export class BidsHedTsvValidator {
* @private
*/
_validateHedColumn() {
if (this.tsvFile.hedColumnHedStrings.length === 0) {
// no HED column strings to validate
return []
}
return this.tsvFile.hedColumnHedStrings.flatMap((hedString, rowIndexMinusTwo) =>
this._validateHedColumnString(hedString, rowIndexMinusTwo + 2),
)
Expand Down Expand Up @@ -230,13 +234,14 @@ export class BidsHedTsvParser {
*/
_parseHedRows(tsvHedRows) {
const hedStrings = []

tsvHedRows.forEach((row, index) => {
const hedString = this._parseHedRow(row, index + 2)
if (hedString !== null) {
hedStrings.push(hedString)
}
})
if (tsvHedRows.size > 0) {
tsvHedRows.forEach((row, index) => {
const hedString = this._parseHedRow(row, index + 2)
if (hedString !== null) {
hedStrings.push(hedString)
}
})
}
return hedStrings
}

Expand All @@ -248,13 +253,15 @@ export class BidsHedTsvParser {
* @private
*/
_mergeEventRows(rowStrings) {
const groupedTsvRows = groupBy(rowStrings, (rowString) => rowString.onset)
const sortedOnsetTimes = Array.from(groupedTsvRows.keys()).sort((a, b) => a - b)
const eventStrings = []
for (const onset of sortedOnsetTimes) {
const onsetRows = groupedTsvRows.get(onset)
const onsetEventString = new BidsTsvEvent(this.tsvFile, onsetRows)
eventStrings.push(onsetEventString)
if (rowStrings.length > 0) {
const groupedTsvRows = groupBy(rowStrings, (rowString) => rowString.onset)
const sortedOnsetTimes = Array.from(groupedTsvRows.keys()).sort((a, b) => a - b)
for (const onset of sortedOnsetTimes) {
const onsetRows = groupedTsvRows.get(onset)
const onsetEventString = new BidsTsvEvent(this.tsvFile, onsetRows)
eventStrings.push(onsetEventString)
}
}
return eventStrings
}
Expand Down
17 changes: 16 additions & 1 deletion tests/bids.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,15 @@ describe('BIDS datasets', () => {
all_bad: new BidsDataset(badDatasets, []),
}
const expectedIssues = {
all_good: [],
all_good: [
BidsHedIssue.fromHedIssue(
generateIssue('duplicateTag', {
tag: 'Boat',
}),
goodDatasets[0].file,
{ tsvLine: 5 },
),
],
all_bad: [
// BidsHedIssue.fromHedIssue(generateIssue('invalidTag', { tag: 'Confused' }), badDatasets[0].file),
BidsHedIssue.fromHedIssue(generateIssue('invalidTag', { tag: 'Confused' }), badDatasets[0].file),
Expand Down Expand Up @@ -220,6 +228,13 @@ describe('BIDS datasets', () => {
badDatasets[3].file,
{ tsvLine: 2 },
),
BidsHedIssue.fromHedIssue(
generateIssue('invalidTopLevelTagGroupTag', {
tag: 'Duration/ferry s',
}),
badDatasets[3].file,
{ tsvLine: 2 },
),
BidsHedIssue.fromHedIssue(
generateIssue('sidecarKeyMissing', {
key: 'purple',
Expand Down
30 changes: 30 additions & 0 deletions tests/bidsErrorData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { BidsHedIssue } from '../bids'
import { generateIssue } from '../common/issues/issues'

export const errorBidsTests = [
{
name: 'invalid-bids-datasets',
description: 'Who knows',
warning: false,
tests: [
{
name: 'valid-sidecar-bad-tag-tsv',
explanation: 'Valid-sidecar, but invalid tsv',
schemaVersion: '8.3.0',
sidecar: {
event_code: {
HED: {
face: '(Red, Blue), (Green, (Yellow))',
},
},
},
sidecarValid: true,
sidecarErrors: [],
eventsString: 'onset\tduration\tHED\n' + '7\t4\tBaloney',
eventsValid: false,
eventsErrors: [BidsHedIssue.fromHedIssue(generateIssue('invalidTag', {}), 'valid-sidecar-invalid-tsv.tsv')],
comboValid: false,
},
],
},
]
132 changes: 132 additions & 0 deletions tests/bidsErrorTests.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import chai from 'chai'
const assert = chai.assert
import { beforeAll, describe, afterAll } from '@jest/globals'
import path from 'path'
import { BidsHedIssue } from '../bids/types/issues'
import { buildSchemas } from '../validator/schema/init'
import { SchemaSpec, SchemasSpec } from '../common/schema/types'
import { BidsDataset, BidsEventFile, BidsHedTsvValidator, BidsSidecar, BidsTsvFile } from '../bids'
import { generateIssue, IssueError } from '../common/issues/issues'

import { HedStringTokenizerOriginal } from '../parser/tokenizerOriginal'
import { HedStringTokenizer } from '../parser/tokenizer'
import { passingBidsTests } from './bidsPassingData'
import { BidsHedTsvParser } from '../bids/validator/bidsHedTsvValidator'
import parseTSV from '../bids/tsvParser'
const fs = require('fs')

const displayLog = process.env.DISPLAY_LOG === 'true'

const skippedErrors = {}

describe('HED tokenizer validation', () => {
const schemaMap = new Map([
['8.2.0', undefined],
['8.3.0', undefined],
])

const badLog = []
let totalTests = 0
let wrongErrors = 0
let unexpectedErrors = 0

beforeAll(async () => {
const spec2 = new SchemaSpec('', '8.2.0', '', path.join(__dirname, '../tests/data/HED8.2.0.xml'))
const specs2 = new SchemasSpec().addSchemaSpec(spec2)
const schemas2 = await buildSchemas(specs2)
const spec3 = new SchemaSpec('', '8.3.0', '', path.join(__dirname, '../tests/data/HED8.3.0.xml'))
const specs3 = new SchemasSpec().addSchemaSpec(spec3)
const schemas3 = await buildSchemas(specs3)
schemaMap.set('8.2.0', schemas2)
schemaMap.set('8.3.0', schemas3)
})

afterAll(() => {
const outBad = path.join(__dirname, 'runLog.txt')
const summary = `Total tests:${totalTests} Unexpected errors:${unexpectedErrors}\n`
if (displayLog) {
fs.writeFileSync(outBad, summary + badLog.join('\n'), 'utf8')
}
})

describe('BIDS validation - validData', () => {
const badLog = []
let totalTests = 0
let unexpectedErrors = 0

beforeAll(async () => {})

afterAll(() => {
const outBad = path.join(__dirname, 'runLog.txt')
const summary = `Total tests:${totalTests} Unexpected errors:${unexpectedErrors}\n`
if (displayLog) {
fs.writeFileSync(outBad, summary + badLog.join('\n'), 'utf8')
}
})

describe.each(passingBidsTests)('$name : $description', ({ tests }) => {
let itemLog

const assertErrors = function (header, issues, iLog) {
iLog.push(`${header}\n`)
totalTests += 1

let errors = []
if (issues.length > 0) {
errors = issues.map((item) => item.hedIssue.hedCode) // list of hedCodes in the issues
}
const errorString = errors.join(',')
if (errors.length > 0) {
iLog.push(`---expected no errors but got errors [${errorString}]\n`)
unexpectedErrors += 1
assert(errors.length === 0, `${header}---expected no errors but got errors [${errorString}]`)
}
}

const validate = function (test, iLog) {
// Make sure that the schema is available
const header = `\n[${test.name} (Expect pass)]`
const thisSchema = schemaMap.get(test.schemaVersion)
assert(thisSchema !== undefined, `${test.schemaVersion} is not available in test ${test.name}`)

// Validate the sidecar by itself
const sidecarName = test.name + '.json'
const bidsSidecar = new BidsSidecar('thisOne', test.sidecar, { relativePath: sidecarName, path: sidecarName })
assert(bidsSidecar instanceof BidsSidecar, 'Test')
const sidecarIssues = bidsSidecar.validate(thisSchema)
assertErrors(header + ':Validating just the sidecar', sidecarIssues, iLog)

// Parse the events file
const eventName = test.name + '.tsv'
const parsedTsv = parseTSV(test.eventsString)
assert(parsedTsv instanceof Map, `${eventName} cannot be parsed`)

// Validate the events file by itself
const bidsTsv = new BidsTsvFile(test.name, parsedTsv, { relativePath: 'eventName' }, [], {})
const validator = new BidsHedTsvValidator(bidsTsv, thisSchema)
validator.validate()
assertErrors(header + ':Parsing events alone', validator.issues, iLog)

// Validate the events file with the sidecar
const bidsTsvSide = new BidsTsvFile(test.name, parsedTsv, { relativePath: 'eventName' }, [], bidsSidecar)
const validatorSide = new BidsHedTsvValidator(bidsTsvSide, thisSchema)
validatorSide.validate()
assertErrors(header + ':Parsing events with ', validatorSide.issues, iLog)
}

beforeAll(async () => {
itemLog = []
})

afterAll(() => {
badLog.push(itemLog.join('\n'))
})

if (tests && tests.length > 0) {
test.each(tests)('BIDS: %s ', (test) => {
validate(test, itemLog)
})
}
})
})
})
73 changes: 73 additions & 0 deletions tests/bidsTestData.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { BidsHedIssue } from '../bids'
import { generateIssue } from '../common/issues/issues'

export const bidsTestData = [
// {
// name: 'valid-bids-datasets',
// description: 'Who knows',
// tests: [
// {
// name: 'no-hed-at-all',
// explanation: 'Neither the sidecar or tsv has HED',
// schemaVersion: '8.3.0',
// sidecar: {
// duration: {
// description: 'Duration of the event in seconds.',
// },
// },
// eventsString: 'onset\tduration\n' + '7\t4',
// sidecarOnlyErrors: [],
// eventsOnlyErrors: [],
// comboErrors: []
// },
// {
// name: 'only-header-in-tsv',
// explanation: 'TSV only has header and some extra white space',
// schemaVersion: '8.3.0',
// sidecar: {
// duration: {
// description: 'Duration of the event in seconds.',
// },
// },
// eventsString: 'onset\tduration\n',
// sidecarOnlyErrors: [],
// eventsOnlyErrors: [],
// comboErrors: []
// },
// ]
// },
{
name: 'invalid-bids-datasets',
description: 'Who knows this',
tests: [
{
name: 'valid-sidecar-bad-tag-tsv',
explanation: 'Valid-sidecar, but invalid tsv',
schemaVersion: '8.3.0',
sidecar: {
event_code: {
HED: {
face: '(Red, Blue), (Green, (Yellow))',
},
},
},
eventsString: 'onset\tduration\tHED\n' + '7\t4\tBaloney',
sidecarOnlyErrors: [],
eventsOnlyErrors: [
BidsHedIssue.fromHedIssue(
generateIssue('invalidTag', { tag: 'Baloney' }),
{ relativePath: 'valid-sidecar-bad-tag-tsv.tsv' },
{ tsvLine: 2 },
),
],
comboErrors: [
BidsHedIssue.fromHedIssue(
generateIssue('invalidTag', { tag: 'Baloney' }),
{ relativePath: 'valid-sidecar-bad-tag-tsv.tsv' },
{ tsvLine: 2 },
),
],
},
],
},
]
Loading

0 comments on commit f6034df

Please sign in to comment.