blob: a9641c50b3241e13fa74a2fd156b9738f6ef5ef7 [file] [log] [blame]
// 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());
}