383 lines
12 KiB
JavaScript
383 lines
12 KiB
JavaScript
// Copyright 2011 The Closure Library Authors. All Rights Reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS-IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
goog.provide('goog.crypt.BlobHasherTest');
|
|
goog.setTestOnly('goog.crypt.BlobHasherTest');
|
|
|
|
goog.require('goog.crypt');
|
|
goog.require('goog.crypt.BlobHasher');
|
|
goog.require('goog.crypt.Md5');
|
|
goog.require('goog.events');
|
|
goog.require('goog.testing.PropertyReplacer');
|
|
goog.require('goog.testing.jsunit');
|
|
|
|
// A browser-independent mock of goog.fs.sliceBlob. The actual implementation
|
|
// calls the underlying slice method differently based on browser version.
|
|
// This mock does not support negative opt_end.
|
|
var fsSliceBlobMock = function(blob, start, opt_end) {
|
|
if (!goog.isNumber(opt_end)) {
|
|
opt_end = blob.size;
|
|
}
|
|
return blob.slice(start, opt_end);
|
|
};
|
|
|
|
// Mock out the Blob using a string.
|
|
BlobMock = function(string) {
|
|
this.data = string;
|
|
this.size = this.data.length;
|
|
};
|
|
|
|
BlobMock.prototype.slice = function(start, end) {
|
|
return new BlobMock(this.data.substr(start, end - start));
|
|
};
|
|
|
|
|
|
// Mock out the FileReader to have control over the flow.
|
|
FileReaderMock = function() {
|
|
this.array_ = [];
|
|
this.result = null;
|
|
this.readyState = this.EMPTY;
|
|
|
|
this.onload = null;
|
|
this.onabort = null;
|
|
this.onerror = null;
|
|
};
|
|
|
|
FileReaderMock.prototype.EMPTY = 0;
|
|
FileReaderMock.prototype.LOADING = 1;
|
|
FileReaderMock.prototype.DONE = 2;
|
|
|
|
FileReaderMock.prototype.mockLoad = function() {
|
|
this.readyState = this.DONE;
|
|
this.result = this.array_;
|
|
if (this.onload) {
|
|
this.onload.call();
|
|
}
|
|
};
|
|
|
|
FileReaderMock.prototype.abort = function() {
|
|
this.readyState = this.DONE;
|
|
if (this.onabort) {
|
|
this.onabort.call();
|
|
}
|
|
};
|
|
|
|
FileReaderMock.prototype.mockError = function() {
|
|
this.readyState = this.DONE;
|
|
if (this.onerror) {
|
|
this.onerror.call();
|
|
}
|
|
};
|
|
|
|
FileReaderMock.prototype.readAsArrayBuffer = function(blobMock) {
|
|
this.readyState = this.LOADING;
|
|
this.array_ = [];
|
|
for (var i = 0; i < blobMock.size; ++i) {
|
|
this.array_[i] = blobMock.data.charCodeAt(i);
|
|
}
|
|
};
|
|
|
|
FileReaderMock.prototype.isLoading = function() {
|
|
return this.readyState == this.LOADING;
|
|
};
|
|
|
|
var stubs = new goog.testing.PropertyReplacer();
|
|
function setUp() {
|
|
stubs.set(goog.global, 'FileReader', FileReaderMock);
|
|
stubs.set(goog.fs, 'sliceBlob', fsSliceBlobMock);
|
|
}
|
|
|
|
function tearDown() {
|
|
stubs.reset();
|
|
}
|
|
|
|
|
|
/**
|
|
* Makes the blobHasher read chunks from the blob and hash it. The number of
|
|
* reads shall not exceed a pre-determined number (typically blob size / chunk
|
|
* size) for computing hash. This function fails fast (after maxReads is
|
|
* reached), assuming that the hasher failed to generate hashes. This prevents
|
|
* the test suite from going into infinite loop.
|
|
* @param {!goog.crypt.BlobHasher} blobHasher Hasher in action.
|
|
* @param {number} maxReads Max number of read attempts.
|
|
*/
|
|
function readFromBlob(blobHasher, maxReads) {
|
|
var counter = 0;
|
|
while (blobHasher.fileReader_ && blobHasher.fileReader_.isLoading() &&
|
|
counter <= maxReads) {
|
|
blobHasher.fileReader_.mockLoad();
|
|
counter++;
|
|
}
|
|
assertTrue(counter <= maxReads);
|
|
return counter;
|
|
}
|
|
|
|
function testBasicOperations() {
|
|
if (!window.Blob) {
|
|
return;
|
|
}
|
|
|
|
// Test hashing with one chunk.
|
|
var hashFn = new goog.crypt.Md5();
|
|
var blobHasher = new goog.crypt.BlobHasher(hashFn);
|
|
var blob = new BlobMock('The quick brown fox jumps over the lazy dog');
|
|
blobHasher.hash(blob);
|
|
readFromBlob(blobHasher, 1);
|
|
assertEquals('9e107d9d372bb6826bd81d3542a419d6',
|
|
goog.crypt.byteArrayToHex(blobHasher.getHash()));
|
|
|
|
// Test hashing with multiple chunks.
|
|
blobHasher = new goog.crypt.BlobHasher(hashFn, 7);
|
|
blobHasher.hash(blob);
|
|
readFromBlob(blobHasher, Math.ceil(blob.size / 7));
|
|
assertEquals('9e107d9d372bb6826bd81d3542a419d6',
|
|
goog.crypt.byteArrayToHex(blobHasher.getHash()));
|
|
|
|
// Test hashing with no chunks.
|
|
blob = new BlobMock('');
|
|
blobHasher.hash(blob);
|
|
readFromBlob(blobHasher, 1);
|
|
assertEquals('d41d8cd98f00b204e9800998ecf8427e',
|
|
goog.crypt.byteArrayToHex(blobHasher.getHash()));
|
|
|
|
}
|
|
|
|
function testNormalFlow() {
|
|
if (!window.Blob) {
|
|
return;
|
|
}
|
|
|
|
// Test the flow with one chunk.
|
|
var hashFn = new goog.crypt.Md5();
|
|
var blobHasher = new goog.crypt.BlobHasher(hashFn, 13);
|
|
var blob = new BlobMock('short');
|
|
var startedEvents = 0;
|
|
var progressEvents = 0;
|
|
var completeEvents = 0;
|
|
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.STARTED,
|
|
function() { ++startedEvents; });
|
|
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.PROGRESS,
|
|
function() { ++progressEvents; });
|
|
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.COMPLETE,
|
|
function() { ++completeEvents; });
|
|
blobHasher.hash(blob);
|
|
assertEquals(1, startedEvents);
|
|
assertEquals(0, progressEvents);
|
|
assertEquals(0, completeEvents);
|
|
readFromBlob(blobHasher, 1);
|
|
assertEquals(1, startedEvents);
|
|
assertEquals(1, progressEvents);
|
|
assertEquals(1, completeEvents);
|
|
|
|
// Test the flow with multiple chunks.
|
|
blob = new BlobMock('The quick brown fox jumps over the lazy dog');
|
|
startedEvents = 0;
|
|
progressEvents = 0;
|
|
completeEvents = 0;
|
|
var progressLoops = 0;
|
|
blobHasher.hash(blob);
|
|
assertEquals(1, startedEvents);
|
|
assertEquals(0, progressEvents);
|
|
assertEquals(0, completeEvents);
|
|
progressLoops = readFromBlob(blobHasher, Math.ceil(blob.size / 13));
|
|
assertEquals(1, startedEvents);
|
|
assertEquals(progressLoops, progressEvents);
|
|
assertEquals(1, completeEvents);
|
|
}
|
|
|
|
function testAbortsAndErrors() {
|
|
if (!window.Blob) {
|
|
return;
|
|
}
|
|
|
|
var hashFn = new goog.crypt.Md5();
|
|
var blobHasher = new goog.crypt.BlobHasher(hashFn, 13);
|
|
var blob = new BlobMock('The quick brown fox jumps over the lazy dog');
|
|
var abortEvents = 0;
|
|
var errorEvents = 0;
|
|
var completeEvents = 0;
|
|
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.ABORT,
|
|
function() { ++abortEvents; });
|
|
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.ERROR,
|
|
function() { ++errorEvents; });
|
|
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.COMPLETE,
|
|
function() { ++completeEvents; });
|
|
|
|
// Immediate abort.
|
|
blobHasher.hash(blob);
|
|
assertEquals(0, abortEvents);
|
|
assertEquals(0, errorEvents);
|
|
assertEquals(0, completeEvents);
|
|
blobHasher.abort();
|
|
blobHasher.abort();
|
|
assertEquals(1, abortEvents);
|
|
assertEquals(0, errorEvents);
|
|
assertEquals(0, completeEvents);
|
|
abortEvents = 0;
|
|
|
|
// Delayed abort.
|
|
blobHasher.hash(blob);
|
|
blobHasher.fileReader_.mockLoad();
|
|
assertEquals(0, abortEvents);
|
|
assertEquals(0, errorEvents);
|
|
assertEquals(0, completeEvents);
|
|
blobHasher.abort();
|
|
blobHasher.abort();
|
|
assertEquals(1, abortEvents);
|
|
assertEquals(0, errorEvents);
|
|
assertEquals(0, completeEvents);
|
|
abortEvents = 0;
|
|
|
|
// Immediate error.
|
|
blobHasher.hash(blob);
|
|
blobHasher.fileReader_.mockError();
|
|
assertEquals(0, abortEvents);
|
|
assertEquals(1, errorEvents);
|
|
assertEquals(0, completeEvents);
|
|
errorEvents = 0;
|
|
|
|
// Delayed error.
|
|
blobHasher.hash(blob);
|
|
blobHasher.fileReader_.mockLoad();
|
|
blobHasher.fileReader_.mockError();
|
|
assertEquals(0, abortEvents);
|
|
assertEquals(1, errorEvents);
|
|
assertEquals(0, completeEvents);
|
|
abortEvents = 0;
|
|
|
|
}
|
|
|
|
function testBasicThrottling() {
|
|
if (!window.Blob) {
|
|
return;
|
|
}
|
|
|
|
var hashFn = new goog.crypt.Md5();
|
|
var blobHasher = new goog.crypt.BlobHasher(hashFn, 5);
|
|
var blob = new BlobMock('The quick brown fox jumps over the lazy dog');
|
|
var throttledEvents = 0;
|
|
var completeEvents = 0;
|
|
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.THROTTLED,
|
|
function() { ++throttledEvents; });
|
|
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.COMPLETE,
|
|
function() { ++completeEvents; });
|
|
|
|
// Start a throttled hash. No chunks should be processed yet.
|
|
blobHasher.setHashingLimit(0);
|
|
assertEquals(0, throttledEvents);
|
|
blobHasher.hash(blob);
|
|
assertEquals(1, throttledEvents);
|
|
assertEquals(0, blobHasher.getBytesProcessed());
|
|
assertNull(blobHasher.fileReader_);
|
|
|
|
// One chunk should be processed.
|
|
blobHasher.setHashingLimit(4);
|
|
assertEquals(1, throttledEvents);
|
|
assertEquals(1, readFromBlob(blobHasher, 1));
|
|
assertEquals(2, throttledEvents);
|
|
assertEquals(4, blobHasher.getBytesProcessed());
|
|
|
|
// One more chunk should be processed.
|
|
blobHasher.setHashingLimit(5);
|
|
assertEquals(2, throttledEvents);
|
|
assertEquals(1, readFromBlob(blobHasher, 1));
|
|
assertEquals(3, throttledEvents);
|
|
assertEquals(5, blobHasher.getBytesProcessed());
|
|
|
|
// Two more chunks should be processed.
|
|
blobHasher.setHashingLimit(15);
|
|
assertEquals(3, throttledEvents);
|
|
assertEquals(2, readFromBlob(blobHasher, 2));
|
|
assertEquals(4, throttledEvents);
|
|
assertEquals(15, blobHasher.getBytesProcessed());
|
|
|
|
// The entire blob should be processed.
|
|
blobHasher.setHashingLimit(Infinity);
|
|
var expectedChunks = Math.ceil(blob.size / 5) - 3;
|
|
assertEquals(expectedChunks, readFromBlob(blobHasher, expectedChunks));
|
|
assertEquals(4, throttledEvents);
|
|
assertEquals(1, completeEvents);
|
|
assertEquals('9e107d9d372bb6826bd81d3542a419d6',
|
|
goog.crypt.byteArrayToHex(blobHasher.getHash()));
|
|
}
|
|
|
|
function testLengthZeroThrottling() {
|
|
if (!window.Blob) {
|
|
return;
|
|
}
|
|
|
|
var hashFn = new goog.crypt.Md5();
|
|
var blobHasher = new goog.crypt.BlobHasher(hashFn);
|
|
var throttledEvents = 0;
|
|
var completeEvents = 0;
|
|
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.THROTTLED,
|
|
function() { ++throttledEvents; });
|
|
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.COMPLETE,
|
|
function() { ++completeEvents; });
|
|
|
|
// Test throttling with length 0 blob.
|
|
var blob = new BlobMock('');
|
|
blobHasher.setHashingLimit(0);
|
|
blobHasher.hash(blob);
|
|
assertEquals(0, throttledEvents);
|
|
assertEquals(1, completeEvents);
|
|
assertEquals('d41d8cd98f00b204e9800998ecf8427e',
|
|
goog.crypt.byteArrayToHex(blobHasher.getHash()));
|
|
}
|
|
|
|
function testAbortsAndErrorsWhileThrottling() {
|
|
if (!window.Blob) {
|
|
return;
|
|
}
|
|
|
|
var hashFn = new goog.crypt.Md5();
|
|
var blobHasher = new goog.crypt.BlobHasher(hashFn, 5);
|
|
var blob = new BlobMock('The quick brown fox jumps over the lazy dog');
|
|
var abortEvents = 0;
|
|
var errorEvents = 0;
|
|
var throttledEvents = 0;
|
|
var completeEvents = 0;
|
|
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.ABORT,
|
|
function() { ++abortEvents; });
|
|
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.ERROR,
|
|
function() { ++errorEvents; });
|
|
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.THROTTLED,
|
|
function() { ++throttledEvents; });
|
|
goog.events.listen(blobHasher, goog.crypt.BlobHasher.EventType.COMPLETE,
|
|
function() { ++completeEvents; });
|
|
|
|
// Test that processing cannot be continued after abort.
|
|
blobHasher.setHashingLimit(0);
|
|
blobHasher.hash(blob);
|
|
assertEquals(1, throttledEvents);
|
|
blobHasher.abort();
|
|
assertEquals(1, abortEvents);
|
|
blobHasher.setHashingLimit(10);
|
|
assertNull(blobHasher.fileReader_);
|
|
assertEquals(1, throttledEvents);
|
|
assertEquals(0, completeEvents);
|
|
assertNull(blobHasher.getHash());
|
|
|
|
// Test that processing cannot be continued after error.
|
|
blobHasher.hash(blob);
|
|
assertEquals(1, throttledEvents);
|
|
blobHasher.fileReader_.mockError();
|
|
assertEquals(1, errorEvents);
|
|
blobHasher.setHashingLimit(100);
|
|
assertNull(blobHasher.fileReader_);
|
|
assertEquals(1, throttledEvents);
|
|
assertEquals(0, completeEvents);
|
|
assertNull(blobHasher.getHash());
|
|
}
|