import * as assert from 'assert' ; import * as async from 'async' ; import SharpImage from './../util/SharpImage' ; import SharpImageLuminanceSource from './../SharpImageLuminanceSource' ; import BarcodeFormat from './../../../core/BarcodeFormat' ; import BinaryBitmap from './../../../core/BinaryBitmap' ; import DecodeHintType from './../../../core/DecodeHintType' ; import './../../../core/InvertedLuminanceSource' ; import LuminanceSource from './../../../core/LuminanceSource' ; import Reader from './../../../core/Reader' ; import Result from './../../../core/Result' ; import ResultMetadataType from './../../../core/ResultMetadataType' ; import TestResult from './../common/TestResult' ; import HybridBinarizer from './../../../core/common/HybridBinarizer' ; import StringEncoding from './../../../core/util/StringEncoding' ; import { TextDecoder } from 'text-encoding' ; const fs = require ( 'fs' ) ; const path = require ( 'path' ) ; author Sean Owen
 *  author dswitkin google abstract class AbstractBlackBoxTestCase { private testBase : string ; private testResults : Array < TestResult > ; public static buildTestBase ( testBasePathSuffix ) { let testBase = path . resolve ( testBasePathSuffix ) ; return testBase ; } protected constructor ( testBasePathSuffix , private barcodeReader , private expectedFormat ) { this . testBase = AbstractBlackBoxTestCase . buildTestBase ( testBasePathSuffix ) ; this . testResults = new Array < TestResult > ( ) ; } protected getTestBase ( ) { return this . testBase ; } protected addTest ( mustPassCount , tryHarderCount , rotation ) { this . addTestWithMax ( mustPassCount , tryHarderCount , 0 , 0 , rotation ) ; } param mustPassCount param tryHarderCount param maxMisreads param maxTryHarderMisreads param rotation protected addTestWithMax ( mustPassCount , tryHarderCount , maxMisreads = 0 , maxTryHarderMisreads = 0 , rotation ) { this . testResults . push ( new TestResult ( mustPassCount , tryHarderCount , maxMisreads , maxTryHarderMisreads , rotation ) ) ; } private walkDirectory ( dirPath ) { const me = this ; let results = new Array < string > ( ) ; const dir = path . resolve ( this . testBase , dirPath ) ; const list = fs . readdirSync ( dir ) ; list . forEach ( ( file ) => { file = path . join ( dir , file ) ; const stat = fs . statSync ( file ) ; if ( stat && stat . isDirectory ( ) ) { results = results . concat ( me . walkDirectory ( file ) ) ; } else { if ( [ ".jpg" , ".jpeg" , ".gif" , ".png" ] . indexOf ( path . extname ( file ) ) !== - 1 ) { results . push ( file ) ; } } } ) ; if ( results . length === 0 ) { console . log ( `No files in folder ${ dir }` ) ; } return results ; } protected getImageFiles ( ) { assert . strictEqual ( fs . existsSync ( this . testBase ) , true , "Please download and install test images, and run from the 'core' directory" ) ; const paths = this . walkDirectory ( this . testBase ) ; return paths ; } protected getReader ( ) { return this . barcodeReader ; } public testBlackBox ( done ) { this . testBlackBoxCountingResults ( true , done ) ; } private testBlackBoxCountingResults ( assertOnFailure , done ) { assert . strictEqual ( this . testResults . length > 0 , true ) ; const imageFiles = this . getImageFiles ( ) ; const testCount = this . testResults . length ; const passedCounts = new Int32Array ( testCount ) ; const misreadCounts = new Int32Array ( testCount ) ; const tryHarderCounts = new Int32Array ( testCount ) ; const tryHarderMisreadCounts = new Int32Array ( testCount ) ; const me = this ; async . eachSeries ( imageFiles , ( testImage , callback ) => { const rotations = [ 0 , 90 , 180 , 270 ] ; SharpImage . loadWithRotations ( testImage , rotations , ( err , images ) => { console . log ( `Starting ${ testImage }` ) ; if ( err ) { callback ( err ) ; } else { const fileBaseName = path . basename ( testImage , path . extname ( testImage ) ) ; let expectedTextFile = path . resolve ( this . testBase , fileBaseName + ".txt" ) ; let expectedText ; if ( fs . existsSync ( expectedTextFile ) ) { expectedText = AbstractBlackBoxTestCase . readTextFileAsString ( expectedTextFile ) ; } else { expectedTextFile = path . resolve ( fileBaseName + ".bin" ) ; assert . strictEqual ( fs . existsSync ( expectedTextFile ) , true , 'result bin/text file should exists' ) ; expectedText = AbstractBlackBoxTestCase . readBinFileAsString ( expectedTextFile ) ; } const expectedMetadataFile = path . resolve ( fileBaseName + ".metadata.txt" ) ; let expectedMetadata = null ; if ( fs . existsSync ( expectedMetadataFile ) ) { expectedMetadata = AbstractBlackBoxTestCase . readTextFileAsMetadata ( expectedMetadataFile ) ; } for ( let x = 0 ; x < testCount ; x ++ ) { const rotation = this . testResults [ x ] . getRotation ( ) ; const rotatedImage = images . get ( rotation ) ; const source = new SharpImageLuminanceSource ( rotatedImage ) ; const bitmap = new BinaryBitmap ( new HybridBinarizer ( source ) ) ; try { if ( me . decode ( bitmap , rotation , expectedText , expectedMetadata , false ) ) { passedCounts [ x ] ++ ; } else { misreadCounts [ x ] ++ ; } } catch ( e ) { console . log ( `could not read at rotation ${ rotation } failed with ${ e . type }` ) ; } try { if ( me . decode ( bitmap , rotation , expectedText , expectedMetadata , true ) ) { tryHarderCounts [ x ] ++ ; } else { tryHarderMisreadCounts [ x ] ++ ; } } catch ( e ) { console . log ( `could not read at rotation ${ rotation } w/TH failed with ${ e . type }` ) ; } } callback ( ) ; } } ) ; } , ( err ) => { if ( err ) { assert . ok ( false , err . toString ( ) ) ; } else { let totalFound = 0 ; let totalMustPass = 0 ; let totalMisread = 0 ; let totalMaxMisread = 0 ; for ( let x = 0 , length = me . testResults . length ; x < length ; x ++ ) { const testResult = me . testResults [ x ] ; console . log ( `Rotation ${ testResult . getRotation ( ) } degrees:` ) ; console . log ( ` ${ passedCounts [ x ] } of ${ imageFiles . length } images passed (${ testResult . getMustPassCount ( ) } required)` ) ; let failed = imageFiles . length - passedCounts [ x ] ; console . log ( ` ${ misreadCounts [ x ] } failed due to misreads, ${ failed - misreadCounts [ x ] } not detected` ) ; console . log ( ` ${ tryHarderCounts [ x ] } of ${ imageFiles . length } images passed with try harder (${ testResult . getTryHarderCount ( ) } required)` ) ; failed = imageFiles . length - tryHarderCounts [ x ] ; console . log ( ` ${ tryHarderMisreadCounts [ x ] } failed due to misreads, ${ failed - tryHarderMisreadCounts [ x ] } not detected` ) ; totalFound += passedCounts [ x ] + tryHarderCounts [ x ] ; totalMustPass += testResult . getMustPassCount ( ) + testResult . getTryHarderCount ( ) ; totalMisread += misreadCounts [ x ] + tryHarderMisreadCounts [ x ] ; totalMaxMisread += testResult . getMaxMisreads ( ) + testResult . getMaxTryHarderMisreads ( ) ; } const totalTests = imageFiles . length * testCount * 2 ; console . log ( `Decoded ${ totalFound } images out of ${ totalTests } (${ totalFound * 100 / totalTests }%, ${ totalMustPass } required)` ) ; if ( totalFound > totalMustPass ) { console . warn ( `+++ Test too lax by ${ totalFound - totalMustPass } images` ) ; } else if ( totalFound < totalMustPass ) { console . error ( `--- Test failed by ${ totalMustPass - totalFound } images` ) ; } if ( totalMisread < totalMaxMisread ) { console . warn ( `+++ Test expects too many misreads by ${ totalMaxMisread - totalMisread } images` ) ; } else if ( totalMisread > totalMaxMisread ) { console . error ( `--- Test had too many misreads by ${ totalMisread - totalMaxMisread } images` ) ; } if ( assertOnFailure ) { for ( let x = 0 ; x < testCount ; x ++ ) { const testResult = me . testResults [ x ] ; let label = "Rotation " + testResult . getRotation ( ) + " degrees: Too many images failed" ; assert . strictEqual ( passedCounts [ x ] >= testResult . getMustPassCount ( ) , true , label ) ; assert . strictEqual ( tryHarderCounts [ x ] >= testResult . getTryHarderCount ( ) , true , "Try harder, " + label ) ; label = "Rotation " + testResult . getRotation ( ) + " degrees: Too many images misread" ; assert . strictEqual ( misreadCounts [ x ] <= testResult . getMaxMisreads ( ) , true , label ) ; assert . strictEqual ( tryHarderMisreadCounts [ x ] <= testResult . getMaxTryHarderMisreads ( ) , true , "Try harder, " + label ) ; } } } done ( ) ; } ) ; } private decode ( source , rotation , expectedText , expectedMetadata , tryHarder ) { const suffix = ` (${ tryHarder ? "try harder, " : "" }rotation: ${ rotation })` ; const hints = new Map < DecodeHintType , any > ( ) ; if ( tryHarder ) { hints . set ( DecodeHintType . TRY_HARDER , true ) ; } let result = null ; try { const pureHints = new Map < DecodeHintType , any > ( hints ) ; pureHints . set ( DecodeHintType . PURE_BARCODE , true ) ; result = this . barcodeReader . decode ( source , pureHints ) ; } catch ( re ) { } if ( result === null ) { result = this . barcodeReader . decode ( source , hints ) ; } if ( this . expectedFormat !== result . getBarcodeFormat ( ) ) { console . warn ( `Format mismatch: expected '${ this . expectedFormat }' but got '${ result . getBarcodeFormat ( ) }'${ suffix }` ) ; return false ; } const resultText = result . getText ( ) ; const expectedTextR = expectedText . replace ( /\r\n/g , '\n' ) ; const resultTextR = resultText . replace ( /\r\n/g , '\n' ) ; if ( expectedTextR !== resultTextR ) { const expectedTextHexCodes = AbstractBlackBoxTestCase . toDebugHexStringCodes ( expectedTextR ) ; const resultTextHexCodes = AbstractBlackBoxTestCase . toDebugHexStringCodes ( resultTextR ) ; console . warn ( `Content mismatch: expected '${ expectedTextR }' (${ expectedTextHexCodes }) but got '${ resultTextR }'${ suffix } (${ resultTextHexCodes })` ) ; return false ; } const resultMetadata = result . getResultMetadata ( ) ; if ( null !== expectedMetadata && undefined !== expectedMetadata ) { for ( let key in expectedMetadata . keys ( ) ) { const expectedValue = expectedMetadata . get ( key ) ; const keyType = AbstractBlackBoxTestCase . valueOfResultMetadataTypeFromString ( key ) ; const actualValue = resultMetadata === null ? undefined : resultMetadata . get ( keyType ) ; if ( expectedValue !== actualValue ) { console . warn ( `Metadata mismatch for key '${ key }': expected '${ expectedValue }' but got '${ actualValue }'` ) ; return false ; } } } return true ; } private static toDebugHexStringCodes ( text ) { let r = "" ; for ( let i = 0 , length = text . length ; i != length ; i ++ ) { if ( i > 0 ) r += ', ' ; r += '0x' + text . charCodeAt ( i ) . toString ( 16 ) . toUpperCase ( ) ; } return r ; } private static valueOfResultMetadataTypeFromString ( value ) { switch ( value ) { case 'OTHER' : return ResultMetadataType . OTHER ; case 'ORIENTATION' : return ResultMetadataType . ORIENTATION ; case 'BYTE_SEGMENTS' : return ResultMetadataType . BYTE_SEGMENTS ; case 'ERROR_CORRECTION_LEVEL' : return ResultMetadataType . ERROR_CORRECTION_LEVEL ; case 'ISSUE_NUMBER' : return ResultMetadataType . ISSUE_NUMBER ; case 'SUGGESTED_PRICE' : return ResultMetadataType . SUGGESTED_PRICE ; case 'POSSIBLE_COUNTRY' : return ResultMetadataType . POSSIBLE_COUNTRY ; case 'UPC_EAN_EXTENSION' : return ResultMetadataType . UPC_EAN_EXTENSION ; case 'PDF417_EXTRA_METADATA' : return ResultMetadataType . PDF417_EXTRA_METADATA ; case 'STRUCTURED_APPEND_SEQUENCE' : return ResultMetadataType . STRUCTURED_APPEND_SEQUENCE ; case 'STRUCTURED_APPEND_PARITY' : return ResultMetadataType . STRUCTURED_APPEND_PARITY ; default : throw value + ' not a ResultMetadataType' ; } } protected static readTextFileAsString ( file ) { const stringContents = fs . readFileSync ( file , { encoding : "utf8" } ) ; if ( stringContents . endsWith ( "\n" ) ) { console . warn ( "contents: string of file " + file + " end with a newline. " + "This may not be intended and cause a test failure" ) ; } return stringContents ; } protected static readBinFileAsString ( file ) { const bufferContents = fs . readFileSync ( file ) ; const stringContents = StringEncoding . decode ( new Uint8Array ( bufferContents ) , "iso-8859-1" ) ; if ( stringContents . endsWith ( "\n" ) ) { console . warn ( "contents: string of file " + file + " end with a newline. " + "This may not be intended and cause a test failure" ) ; } return stringContents ; } protected static readTextFileAsMetadata ( file ) { return null ; } } export default AbstractBlackBoxTestCase ;