diff --git a/CHANGELOG.md b/CHANGELOG.md index 97597938b..f828e966a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [3.6.2] 2023-01-03 + +### Changed + - Fix [#56](https://github.com/mmomtchev/node-gdal-async/issues/56), propagate input errors in `calcAsync` and `RasterMuxStream` + ## [3.6.1] 2022-12-21 ### Added diff --git a/codecov.yml b/codecov.yml index 20c6627ec..dca05d81b 100644 --- a/codecov.yml +++ b/codecov.yml @@ -14,4 +14,6 @@ coverage: - "src" - "lib" patch: - default: 80% + default: + target: auto + threshold: 2% diff --git a/lib/calc.js b/lib/calc.js index 7d392da74..154853e85 100644 --- a/lib/calc.js +++ b/lib/calc.js @@ -113,7 +113,11 @@ const calc = (gdal) => function calcAsync(inputs, output, fn, options) { mux.on('data', (chunk) => { processed += chunk[Object.keys(chunk)[0]].length const done = processed / length - progress(done) + try { + progress(done) + } catch (e) { + reject(e) + } }) } diff --git a/lib/multiplexer.js b/lib/multiplexer.js index 8014b53ca..b59b71f47 100644 --- a/lib/multiplexer.js +++ b/lib/multiplexer.js @@ -65,6 +65,7 @@ class RasterMuxStream extends Readable { this.endHandlers = {} this.blockOptimize = (options || {}).blockOptimize this.rasterHighWaterMark = Infinity + for (const id of this.ids) { const inp = inputs[id] if (!(inp instanceof Readable)) throw new TypeError('inputs must be a map of Readables') @@ -80,6 +81,8 @@ class RasterMuxStream extends Readable { this.endHandlers[id] = this.inputEnded.bind(this, id) this.inputs[id].on('end', this.endHandlers[id]) + + this.inputs[id].on('error', this.errorHandler.bind(this)) } } @@ -194,6 +197,11 @@ class RasterMuxStream extends Readable { this.tryEnd() } + errorHandler(e) { + debug('emitting error', e) + this.destroy(e) + } + _read() { debug('readStart') this.throttle(true) diff --git a/test/api_utils.test.ts b/test/api_utils.test.ts index f68d79d16..a8dd7e64d 100644 --- a/test/api_utils.test.ts +++ b/test/api_utils.test.ts @@ -593,6 +593,51 @@ describe('gdal_utils', () => { ) }) + it('should reject when the file is corrupted', function () { + const tempFile = `/vsimem/invalid_calc_${String(Math.random()).substring(2)}.tiff` + let T2m, D2m + try { + T2m = gdal.open(path.resolve(__dirname, 'data','AROME_T2m_10.tiff')) + D2m = gdal.open(path.resolve(__dirname, 'data','truncated.tiff')) + } catch (e) { + // Older GDAL versions cannot open the truncated file, so this is + // considered a successful test too + this.skip() + } + const size = T2m.rasterSize + const espyFn = (t: number, td: number) => 125 * (t - td) + return assert.isRejected( + gdal.calcAsync({ + A: T2m.bands.get(1), + B: D2m.bands.get(1) + }, + gdal.open(tempFile, 'w', 'GTiff', size.x, size.y, 1, gdal.GDT_Float64).bands.get(1), + espyFn), + /Cannot read/ + ) + }) + + it('should reject on progress exception', () => { + const tempFile = `/vsimem/invalid_calc_${String(Math.random()).substring(2)}.tiff` + const T2m = gdal.open(path.resolve(__dirname, 'data','AROME_T2m_10.tiff')) + const D2m = gdal.open(path.resolve(__dirname, 'data','AROME_D2m_10.tiff')) + const size = T2m.rasterSize + const espyFn = (t: number, td: number) => 125 * (t - td) + return assert.isRejected( + gdal.calcAsync({ + A: T2m.bands.get(1), + B: D2m.bands.get(1) + }, + gdal.open(tempFile, 'w', 'GTiff', size.x, size.y, 1, gdal.GDT_Float64).bands.get(1), + espyFn, + { progress_cb: () => { + throw new Error('progress error') + } } + ), + /progress error/ + ) + }) + it('should reject when raster sizes do not match', () => { const tempFile = `/vsimem/invalid_calc_${String(Math.random()).substring(2)}.tiff` const espyFn = (t: number, td: number) => 125 * (t - td) diff --git a/test/data/truncated.tiff b/test/data/truncated.tiff new file mode 100644 index 000000000..7ef64eb90 Binary files /dev/null and b/test/data/truncated.tiff differ