16672: Adds Cypress tests on process logs viewing.
authorLucas Di Pentima <lucas.dipentima@curii.com>
Fri, 25 Mar 2022 20:13:57 +0000 (17:13 -0300)
committerLucas Di Pentima <lucas.dipentima@curii.com>
Fri, 25 Mar 2022 21:01:56 +0000 (18:01 -0300)
Arvados-DCO-1.1-Signed-off-by: Lucas Di Pentima <lucas.dipentima@curii.com>

cypress/integration/process.spec.js [new file with mode: 0644]
cypress/support/commands.js
src/components/multi-panel-view/multi-panel-view.tsx
src/views/process-panel/process-log-form.tsx
src/views/process-panel/process-panel-root.tsx

diff --git a/cypress/integration/process.spec.js b/cypress/integration/process.spec.js
new file mode 100644 (file)
index 0000000..75c318d
--- /dev/null
@@ -0,0 +1,194 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+describe('Process tests', function() {
+    let activeUser;
+    let adminUser;
+
+    before(function() {
+        // Only set up common users once. These aren't set up as aliases because
+        // aliases are cleaned up after every test. Also it doesn't make sense
+        // to set the same users on beforeEach() over and over again, so we
+        // separate a little from Cypress' 'Best Practices' here.
+        cy.getUser('admin', 'Admin', 'User', true, true)
+            .as('adminUser').then(function() {
+                adminUser = this.adminUser;
+            }
+        );
+        cy.getUser('user', 'Active', 'User', false, true)
+            .as('activeUser').then(function() {
+                activeUser = this.activeUser;
+            }
+        );
+    });
+
+    beforeEach(function() {
+        cy.clearCookies();
+        cy.clearLocalStorage();
+    });
+
+    function setupDockerImage(image_name) {
+        // Create a collection that will be used as a docker image for the tests.
+        cy.createCollection(adminUser.token, {
+            name: 'docker_image',
+            manifest_text: ". d21353cfe035e3e384563ee55eadbb2f+67108864 5c77a43e329b9838cbec18ff42790e57+55605760 0:122714624:sha256:d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678.tar\n"
+        }).as('dockerImage').then(function(dockerImage) {
+            // Give read permissions to the active user on the docker image.
+            cy.createLink(adminUser.token, {
+                link_class: 'permission',
+                name: 'can_read',
+                tail_uuid: activeUser.user.uuid,
+                head_uuid: dockerImage.uuid
+            }).as('dockerImagePermission').then(function() {
+                // Set-up docker image collection tags
+                cy.createLink(activeUser.token, {
+                    link_class: 'docker_image_repo+tag',
+                    name: image_name,
+                    head_uuid: dockerImage.uuid,
+                }).as('dockerImageRepoTag');
+                cy.createLink(activeUser.token, {
+                    link_class: 'docker_image_hash',
+                    name: 'sha256:d8309758b8fe2c81034ffc8a10c36460b77db7bc5e7b448c4e5b684f9d95a678',
+                    head_uuid: dockerImage.uuid,
+                }).as('dockerImageHash');
+            })
+        });
+        return cy.getAll('@dockerImage', '@dockerImageRepoTag', '@dockerImageHash',
+            '@dockerImagePermission').then(function([dockerImage]) {
+                return dockerImage;
+            });
+    }
+
+    function createContainerRequest(user, name, docker_image, command, reuse = false, state = 'Uncommitted') {
+        return setupDockerImage(docker_image).then(function(dockerImage) {
+            return cy.createContainerRequest(user.token, {
+                name: name,
+                command: command,
+                container_image: dockerImage.portable_data_hash, // for some reason, docker_image doesn't work here
+                output_path: 'stdout.txt',
+                priority: 1,
+                runtime_constraints: {
+                    vcpus: 1,
+                    ram: 1,
+                },
+                use_existing: reuse,
+                state: state,
+                mounts: {
+                    foo: {
+                        kind: 'tmp',
+                        path: '/tmp/foo',
+                    }
+                }
+            });
+        });
+    }
+
+    it('shows process logs', function() {
+        const crName = 'test_container_request';
+        createContainerRequest(
+            activeUser,
+            crName,
+            'arvados/jobs',
+            ['echo', 'hello world'],
+            false, 'Committed')
+        .then(function(containerRequest) {
+            cy.loginAs(activeUser);
+            cy.goToPath(`/processes/${containerRequest.uuid}`);
+            cy.get('[data-cy=process-info]').should('contain', crName);
+            cy.get('[data-cy=process-logs]')
+                .should('contain', 'No logs yet')
+                .and('not.contain', 'hello world');
+            cy.createLog(activeUser.token, {
+                object_uuid: containerRequest.container_uuid,
+                properties: {
+                    text: 'hello world'
+                },
+                event_type: 'stdout'
+            }).then(function(log) {
+                cy.get('[data-cy=process-logs]')
+                    .should('not.contain', 'No logs yet')
+                    .and('contain', 'hello world');
+            })
+        });
+    });
+
+    it('filters process logs by event type', function() {
+        const nodeInfoLogs = [
+            'Host Information',
+            'Linux compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p 5.4.0-1059-azure #62~18.04.1-Ubuntu SMP Tue Sep 14 17:53:18 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux',
+            'CPU Information',
+            'processor  : 0',
+            'vendor_id  : GenuineIntel',
+            'cpu family : 6',
+            'model      : 79',
+            'model name : Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz'
+        ];
+        const crunchRunLogs = [
+            '2022-03-22T13:56:22.542417997Z using local keepstore process (pid 3733) at http://localhost:46837, writing logs to keepstore.txt in log collection',
+            '2022-03-22T13:56:26.237571754Z crunch-run 2.4.0~dev20220321141729 (go1.17.1) started',
+            '2022-03-22T13:56:26.244704134Z crunch-run process has uid=0(root) gid=0(root) groups=0(root)',
+            '2022-03-22T13:56:26.244862836Z Executing container \'zzzzz-dz642-1wokwvcct9s9du3\' using docker runtime',
+            '2022-03-22T13:56:26.245037738Z Executing on host \'compute-99cb150b26149780de44b929577e1aed-19rgca8vobuvc4p\'',
+        ];
+        const stdoutLogs = [
+            'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec dui nisi, hendrerit porta sapien a, pretium dignissim purus.',
+            'Integer viverra, mauris finibus aliquet ultricies, dui mauris cursus justo, ut venenatis nibh ex eget neque.',
+            'In hac habitasse platea dictumst.',
+            'Fusce fringilla turpis id accumsan faucibus. Donec congue congue ex non posuere. In semper mi quis tristique rhoncus.',
+            'Interdum et malesuada fames ac ante ipsum primis in faucibus.',
+            'Quisque fermentum tortor ex, ut suscipit velit feugiat faucibus.',
+            'Donec vitae porta risus, at luctus nulla. Mauris gravida iaculis ipsum, id sagittis tortor egestas ac.',
+            'Maecenas condimentum volutpat nulla. Integer lacinia maximus risus eu posuere.',
+            'Donec vitae leo id augue gravida bibendum.',
+            'Nam libero libero, pretium ac faucibus elementum, mattis nec ex.',
+            'Nullam id laoreet nibh. Vivamus tellus metus, pretium quis justo ut, bibendum varius metus. Pellentesque vitae accumsan lorem, quis tincidunt augue.',
+            'Aliquam viverra nisi nulla, et efficitur dolor mattis in.',
+            'Sed at enim sit amet nulla tincidunt mattis. Aenean eget aliquet ex, non ultrices ex. Nulla ex tortor, vestibulum aliquam tempor ac, aliquam vel est.',
+            'Fusce auctor faucibus libero id venenatis. Etiam sodales, odio eu cursus efficitur, quam sem blandit ex, quis porttitor enim dui quis lectus. In id tincidunt felis.',
+            'Phasellus non ex quis arcu tempus faucibus molestie in sapien.',
+            'Duis tristique semper dolor, vitae pulvinar risus.',
+            'Aliquam tortor elit, luctus nec tortor eget, porta tristique nulla.',
+            'Nulla eget mollis ipsum.',
+        ];
+
+        createContainerRequest(
+            activeUser,
+            'test_container_request',
+            'arvados/jobs',
+            ['echo', 'hello world'],
+            false, 'Committed')
+        .then(function(containerRequest) {
+            cy.logsForContainer(activeUser.token, containerRequest.container_uuid,
+                'node-info', nodeInfoLogs).as('nodeInfoLogs');
+            cy.logsForContainer(activeUser.token, containerRequest.container_uuid,
+                'crunch-run', crunchRunLogs).as('crunchRunLogs');
+            cy.logsForContainer(activeUser.token, containerRequest.container_uuid,
+                'stdout', stdoutLogs).as('stdoutLogs');
+            cy.getAll('@stdoutLogs', '@nodeInfoLogs', '@crunchRunLogs').then(function() {
+                cy.loginAs(activeUser);
+                cy.goToPath(`/processes/${containerRequest.uuid}`);
+                // Should should all logs
+                cy.get('[data-cy=process-logs-filter]').should('contain', 'All logs');
+                cy.get('[data-cy=process-logs]')
+                    .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
+                    .and('contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
+                    .and('contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
+                // Select 'node-info' logs
+                cy.get('[data-cy=process-logs-filter]').click();
+                cy.get('body').contains('li', 'node-info').click();
+                cy.get('[data-cy=process-logs]')
+                    .should('not.contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
+                    .and('contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
+                    .and('not.contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
+                // Select 'stdout' logs
+                cy.get('[data-cy=process-logs-filter]').click();
+                cy.get('body').contains('li', 'stdout').click();
+                cy.get('[data-cy=process-logs]')
+                    .should('contain', stdoutLogs[Math.floor(Math.random() * stdoutLogs.length)])
+                    .and('not.contain', nodeInfoLogs[Math.floor(Math.random() * nodeInfoLogs.length)])
+                    .and('not.contain', crunchRunLogs[Math.floor(Math.random() * crunchRunLogs.length)]);
+            });
+        });
+    });
+});
\ No newline at end of file
index cfdfa9ecffb0e94c2262d0f580b8222f3d7b2c72..5a2428b2c5e7764f1f8c1c30cb9a221a7bb61ffc 100644 (file)
@@ -133,6 +133,16 @@ Cypress.Commands.add(
     }
 )
 
+Cypress.Commands.add(
+    "getCollection", (token, uuid) => {
+        return cy.doRequest('GET', `/arvados/v1/collections/${uuid}`, null, {}, token)
+            .its('body')
+            .then(function (theCollection) {
+                return theCollection;
+            })
+    }
+)
+
 Cypress.Commands.add(
     "createCollection", (token, data) => {
         return cy.createResource(token, 'collections', {
@@ -150,6 +160,49 @@ Cypress.Commands.add(
     }
 )
 
+Cypress.Commands.add(
+    'createContainerRequest', (token, data) => {
+        return cy.createResource(token, 'container_requests', {
+            container_request: JSON.stringify(data),
+            ensure_unique_name: true
+        })
+    }
+)
+
+Cypress.Commands.add(
+    "updateContainerRequest", (token, uuid, data) => {
+        return cy.updateResource(token, 'container_requests', uuid, {
+            container_request: JSON.stringify(data)
+        })
+    }
+)
+
+Cypress.Commands.add(
+    "createLog", (token, data) => {
+        return cy.createResource(token, 'logs', {
+            log: JSON.stringify(data)
+        })
+    }
+)
+
+Cypress.Commands.add(
+    "logsForContainer", (token, uuid, logType, logTextArray = []) => {
+        let logs = [];
+        for (const logText of logTextArray) {
+            logs.push(cy.createLog(token, {
+                object_uuid: uuid,
+                event_type: logType,
+                properties: {
+                    text: logText
+                }
+            }).as('lastLogRecord'))
+        }
+        cy.getAll('@lastLogRecord').then(function () {
+            return logs;
+        })
+    }
+)
+
 Cypress.Commands.add(
     "createVirtualMachine", (token, data) => {
         return cy.createResource(token, 'virtual_machines', {
index de8249909d3af4ba16c53b49490ec0144bc6b4d0..2bff28cb2bc249c3f07061e8f6a113b5e84c31a5 100644 (file)
@@ -33,6 +33,7 @@ const styles: StyleRulesCallback<CssRules> = theme => ({
     },
     content: {
         overflow: 'auto',
+        display: 'contents',
     },
 });
 
index 6a8e522144d750df223bcec77a42b90f21cd61fa..1f63e28d2af99336d0a5277201f55d774b4cc419 100644 (file)
@@ -40,7 +40,7 @@ type ProcessLogFormProps = ProcessLogFormDataProps & ProcessLogFormActionProps &
 
 export const ProcessLogForm = withStyles(styles)(
     ({ classes, selectedFilter, onChange, filters }: ProcessLogFormProps) =>
-        <form autoComplete="off">
+        <form autoComplete="off" data-cy="process-logs-filter">
             <FormControl className={classes.formControl}>
                 <Select
                     value={selectedFilter.value}
index 3e695a2fb4fafbc428905208866579e3cbb2d6b3..416faec7ef30c7388bf6b0d92d93286a52d7c40a 100644 (file)
@@ -58,7 +58,7 @@ export const ProcessPanelRoot = withStyles(styles)(
     ({ process, processLogsPanel, ...props }: ProcessPanelRootProps) =>
     process
         ? <MPVContainer className={props.classes.root} spacing={8} panelStates={panelsData}  justify-content="flex-start" direction="column" wrap="nowrap">
-            <MPVPanelContent forwardProps xs="auto">
+            <MPVPanelContent forwardProps xs="auto" data-cy="process-info">
                 <ProcessInformationCard
                     process={process}
                     onContextMenu={event => props.onContextMenu(event, process)}
@@ -68,10 +68,10 @@ export const ProcessPanelRoot = withStyles(styles)(
                     cancelProcess={props.cancelProcess}
                 />
             </MPVPanelContent>
-            <MPVPanelContent forwardProps xs="auto">
+            <MPVPanelContent forwardProps xs="auto" data-cy="process-details">
                 <ProcessDetailsCard process={process} />
             </MPVPanelContent>
-            <MPVPanelContent forwardProps xs maxHeight='50%'>
+            <MPVPanelContent forwardProps xs maxHeight='50%' data-cy="process-logs">
                 <ProcessLogsCard
                     onCopy={props.onLogCopyToClipboard}
                     process={process}
@@ -87,7 +87,7 @@ export const ProcessPanelRoot = withStyles(styles)(
                     navigateToLog={props.navigateToLog}
                 />
             </MPVPanelContent>
-            <MPVPanelContent forwardProps xs maxHeight='50%'>
+            <MPVPanelContent forwardProps xs maxHeight='50%' data-cy="process-children">
                 <SubprocessPanel />
             </MPVPanelContent>
         </MPVContainer>