// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import type {MetadataStats} from '../prod/file_manager/shared_types.js';
import {addEntries, createTestFile, ENTRIES, EntryType, RootPath, TestEntryInfo} from '../test_util.js';
import {remoteCall} from './background.js';
import {DirectoryTreePageObject} from './page_objects/directory_tree.js';
import {BASIC_LOCAL_ENTRY_SET} from './test_data.js';
/**
* Check if |value| equals the |desiredValue| within 1% margin of tolerance.
* @param value The variable value.
* @param desiredValue The desired value.
*/
function equal1PercentMargin(value: number, desiredValue: number): boolean {
// floor and ceil to account to at least +/-1 unit.
const minValue = Math.floor(desiredValue * 0.99);
const maxValue = Math.ceil(desiredValue * 1.01);
const result =
value === desiredValue || (minValue <= value && value <= maxValue);
if (!result) {
console.log(
'min value: ' + minValue + ' got value: ' + value +
' max value: ' + maxValue);
}
return result;
}
/**
* Creates a test file, which can be inside another folder, however parent
* folders have to be created by the caller.
* @param path Folder path to be created,
*/
function createTestFolder(path: string): TestEntryInfo {
const name = path.split('/').pop();
return new TestEntryInfo({
targetPath: path,
nameText: name,
type: EntryType.DIRECTORY,
lastModifiedTime: 'Jan 1, 1980, 11:59 PM',
sizeText: '--',
typeText: 'Folder',
});
}
/**
* Creates a Shared Drive.
* @param name Shared Drive name.
*/
function createTestTeamDrive(name: string): TestEntryInfo {
return new TestEntryInfo({
teamDriveName: name,
type: EntryType.SHARED_DRIVE,
capabilities: {
canCopy: true,
canDelete: true,
canRename: true,
canAddChildren: true,
canShare: true,
},
});
}
/**
* Entries used by Drive and Downloads tests.
*/
const testEntries = [
createTestFile('file1.txt'),
createTestFile('file2.txt'),
createTestFile('file3.txt'),
createTestFile('file4.txt'),
createTestFile('file5.txt'),
createTestFile('file6.txt'),
createTestFile('file7.txt'),
createTestFile('file8.txt'),
createTestFolder('photos1'),
createTestFolder('photos1/folder1'),
createTestFolder('photos1/folder2'),
createTestFile('photos1/file1.txt'),
createTestFolder('photos2'),
createTestFolder('photos3'),
];
/**
* Measures the number of metadata operations generated for:
* - Opening Files app in My Drive with 8 files and 3 folders.
* - Navigate to My Drive > photos1 > folder2, which is empty.
*/
export async function metadataDrive() {
// Open Files app on Drive.
const appId = await remoteCall.setupAndWaitUntilReady(
RootPath.DRIVE, testEntries, testEntries);
// Navigate 2 folders deep, because navigating in directory tree might
// trigger further metadata fetches.
const directoryTree = await DirectoryTreePageObject.create(appId);
await directoryTree.navigateToPath('/My Drive/photos1/folder1');
// Fetch the metadata stats.
const metadataStats = await remoteCall.callRemoteTestUtil<MetadataStats>(
'getMetadataStats', appId, []);
// Verify the number of metadata operations generated by the whole
// navigation above.
// If the asserts below fail, check if your change has increased the number
// of metadata operations, because they impact the overall app performance.
// Full fetch tally:
// 8 files in My Drive
// + 3 folders in My Drive.
// + 1 My Drive root.
// + 2 folders when expanding photos1
// = 14
chrome.test.assertEq(14, metadataStats.fullFetch);
chrome.test.assertEq(12, metadataStats.fromCache);
// Cleared 8 files + 3 folders when navigated out of My Drive and
// clearing file list.
chrome.test.assertEq(11, metadataStats.clearCacheCount);
chrome.test.assertEq(0, metadataStats.clearAllCount);
chrome.test.assertEq(0, metadataStats.invalidateCount);
}
/**
* Measures the number of metadata operations generated for:
* - Opening Files app in Downloads with 8 files and 3 folders.
* - Navigate to Downloads > photos1 > folder1 which is empty.
*/
export async function metadataDownloads() {
// Open Files app on Downloads.
const appId = await remoteCall.setupAndWaitUntilReady(
RootPath.DOWNLOADS, testEntries, testEntries);
// Navigate 2 folders deep, because navigating in directory tree might
// triggers further metadata fetches.
const directoryTree = await DirectoryTreePageObject.create(appId);
await directoryTree.navigateToPath('/My files/Downloads/photos1/folder1');
// Fetch the metadata stats.
const metadataStats = await remoteCall.callRemoteTestUtil<MetadataStats>(
'getMetadataStats', appId, []);
// Verify the number of metadata operations generated by the whole
// navigation above.
// If the asserts below fail, check if your change has increased the number
// of metadata operations, because they impact the overall app performance.
// Full fetch tally:
// 8 files in Downloads
// + 3 folders in Downloads.
// + 1 Downloads root.
// + 1 read again photos1 when naviated to it.
// + 8 files in Downloads again.
// = 21
chrome.test.assertEq(21, metadataStats.fullFetch);
// 8 files and 3 folders in Downloads when expanding in the directory
// tree.
chrome.test.assertEq(0, metadataStats.fromCache);
// Cleared 8 files + 3 folders when navigated out of Downloads and
// clearing file list.
chrome.test.assertEq(11, metadataStats.clearCacheCount);
chrome.test.assertEq(0, metadataStats.clearAllCount);
chrome.test.assertEq(0, metadataStats.invalidateCount);
}
/**
* Measures the number of metadata operations generated for:
* - Opening Files app in My Drive with 51 folders.
* - Navigate to My Drive > folder1 which has 50 files.
*
* Using 50 files and 50 folders because in the Drive backend it has a
* throttle for max of 20 concurrent operations.
*/
export async function metadataLargeDrive() {
const entries = [createTestFolder('folder1')];
const folder1ExpectedRows = [];
for (let i = 0; i < 50; i++) {
const testFile = createTestFile('folder1/file-' + i + '.txt');
entries.push(testFile);
folder1ExpectedRows.push(testFile.getExpectedRow());
// Using sibling folders because navigateWithDirectoryTree expands their
// parent directory tree might issue metadata requests the child folders.
entries.push(createTestFile('sibling-folder-' + i));
}
// Open Files app on Drive.
const appId =
await remoteCall.setupAndWaitUntilReady(RootPath.DRIVE, entries, entries);
console.log(' remoteCall.setupAndWaitUntilReady finished!');
// Navigate only 1 folder deep,which is slightly different from
// metadataDrive test.
const directoryTree = await DirectoryTreePageObject.create(appId);
await directoryTree.navigateToPath('/My Drive/folder1');
// Wait for the metadata stats to reach the desired count.
// File list component, doesn't display all files at once for performance
// reasons. Since we can't check the modifiedTime for all files in file
// list, which is a proxy for "all metadata requests have finished", we have
// to wait until the metadata stats to have the expected count.
// If the asserts below fail, check if your change has increased the number
// of metadata operations, because they impact the overall app performance.
const checkMetadata = (metadataStats: MetadataStats) => {
// Full fetch tally:
// 51 files in My Drive.
// + 50 files in My Drive>folder1.
// + 1 My Drive root.
// + 1 read again folder1 when naviated to it.
// = 103
if (!equal1PercentMargin(metadataStats.fullFetch, 103)) {
return false;
}
// 50 team drives cached, reading from file list when navigating to
// /team_drives, then read cached when expanding directory tree.
if (metadataStats.fromCache >= 70) {
return false;
}
// Cleared 51 folders when navigated out of My Drive and clearing file
// list.
if (!equal1PercentMargin(metadataStats.clearCacheCount, 51)) {
return false;
}
if (metadataStats.clearAllCount !== 0) {
return false;
}
if (metadataStats.invalidateCount !== 0) {
return false;
}
return true;
};
await remoteCall.waitFor('getMetadataStats', appId, checkMetadata as any);
}
/**
* Measures the number of metadata operations generated for:
* - Opening Files app in My Drive, with 50 folders and 50 files.
* - Navigate to Shared Drives, with 50 team drives.
* - Expand Shared Drives to display the 50 team drives..
*/
export async function metadataTeamDrives() {
const entries = [];
const driveEntries = [];
// Using 50 files and 50 folders because in the Drive backend it has a
// throttle for max of 20 concurrent jobs.
for (let i = 0; i < 50; i++) {
entries.push(createTestFile('file-' + i + '.txt'));
// Using sibling folders because navigateWithDirectoryTree expands their
// parent and directory_tree.js issues some metadata requests for this
// condition.
entries.push(createTestFolder('sibling-folder-' + i));
driveEntries.push(createTestTeamDrive('Team Drive ' + i));
}
// Downloads just some entries. Load some in Downloads and Downloads to
// check that Downloads entries don't issue metadata calls when navigating
// on Drive/Shared drives.
const downloadsEntries = entries.slice(0, 7);
// Open Files app on Drive.
const appId = await remoteCall.setupAndWaitUntilReady(
RootPath.DRIVE, downloadsEntries, entries.concat(driveEntries));
// Navigate to Shared drives root.
const directoryTree = await DirectoryTreePageObject.create(appId);
await directoryTree.navigateToPath('/Shared drives');
// Expand Shared Drives, because expanding might need metadata.
await directoryTree.expandTreeItemByLabel('Shared drives');
// Get all Shared Drives' children and check that we have 50 team drives.
await directoryTree.waitForChildItemsCountByLabel('Shared drives', 50);
// Fetch the metadata stats.
const metadataStats = await remoteCall.callRemoteTestUtil<MetadataStats>(
'getMetadataStats', appId, []);
// Verify the number of metadata operations generated by the whole
// navigation above.
// If the asserts below fail, check if your change has increased the number
// of metadata operations, because they impact the overall app performance.
//
// Full fetch tally:
// 50 files in My Drive.
// + 50 folders in My Drive.
// + 50 team drives.
// + 1 My Drive root.
// + 1 Shared Drives root.
// = 152
chrome.test.assertEq(152, metadataStats.fullFetch);
// No cache read here because metadata is retrieved from the store instead of
// the metadata model when expanding directory tree.
chrome.test.assertEq(0, metadataStats.fromCache);
// Cleared 50 folders + 50 files when navigated out of My Drive and
// clearing file list.
chrome.test.assertEq(100, metadataStats.clearCacheCount);
chrome.test.assertEq(0, metadataStats.clearAllCount);
chrome.test.assertEq(0, metadataStats.invalidateCount);
}
/**
* Tests that fetching content metadata from a DocumentsProvider completes.
*/
export async function metadataDocumentsProvider() {
// Add files to the DocumentsProvider volume.
await addEntries(['documents_provider'], BASIC_LOCAL_ENTRY_SET);
// Open Files app.
const appId = await remoteCall.openNewWindow(RootPath.DOWNLOADS);
// Wait for the DocumentsProvider volume to mount and click to open the
// DocumentsProvider volume.
const directoryTree = await DirectoryTreePageObject.create(appId);
await directoryTree.selectItemByType('documents_provider');
// Check: the DocumentsProvider files should appear in the file list.
const files = TestEntryInfo.getExpectedRows(BASIC_LOCAL_ENTRY_SET);
await remoteCall.waitForFiles(appId, files, {ignoreLastModifiedTime: true});
// Select file hello.txt in the file list.
await remoteCall.waitUntilSelected(appId, ENTRIES.hello.nameText);
// Check that a request for content metadata completes.
const result = await await remoteCall.callRemoteTestUtil<string[]>(
'getContentMetadata', appId, [['mediaMimeType']]);
// Check nothing in the result was returned.
chrome.test.checkDeepEq([], result);
}