chore: add basic tests to container stats

Adds tests to container stats to confirm that the two donuts are appearing
and have correct values and styling.

Adds a test id to the donut so we can check styling of the arc, and exports
the container stats updateStatistics() function for injecting test data.

Signed-off-by: Tim deBoer <git@tdeboer.ca>
This commit is contained in:
Tim deBoer 2023-10-16 11:35:45 -04:00
parent db4d2c3b15
commit 2153cd07fb
3 changed files with 181 additions and 2 deletions

View file

@ -0,0 +1,174 @@
/**********************************************************************
* Copyright (C) 2023 Red Hat, Inc.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
***********************************************************************/
import '@testing-library/jest-dom/vitest';
import { test, expect, vi, beforeAll } from 'vitest';
import { render, screen } from '@testing-library/svelte';
import ContainerStatistics from './ContainerStatistics.svelte';
import type { ContainerStatsInfo } from '../../../../main/src/plugin/api/container-stats-info';
import { ContainerGroupInfoTypeUI, type ContainerInfoUI } from './ContainerInfoUI';
const myContainer: ContainerInfoUI = {
id: 'foobar',
shortId: 'foobar',
name: 'foobar',
image: 'foobar',
shortImage: 'foobar',
engineId: 'foobar',
engineName: 'foobar',
engineType: 'podman',
state: 'RUNNING',
uptime: 'foobar',
startedAt: 'foobar',
ports: [],
portsAsString: 'foobar',
displayPort: 'foobar',
command: 'foobar',
hasPublicPort: false,
groupInfo: {
name: 'foobar',
type: ContainerGroupInfoTypeUI.STANDALONE,
},
selected: false,
created: 0,
labels: {},
};
const stats: ContainerStatsInfo = {
memory_stats: {
usage: 256,
limit: 1024,
stats: {
total_pgmajfault: 0,
cache: 0,
mapped_file: 0,
total_inactive_file: 0,
pgpgout: 414,
rss: 6537216,
total_mapped_file: 0,
writeback: 0,
unevictable: 0,
pgpgin: 477,
total_unevictable: 0,
pgmajfault: 0,
total_rss: 6537216,
total_rss_huge: 6291456,
total_writeback: 0,
total_inactive_anon: 0,
rss_huge: 6291456,
hierarchical_memory_limit: 67108864,
total_pgfault: 964,
total_active_file: 0,
active_anon: 6537216,
total_active_anon: 6537216,
total_pgpgout: 414,
total_cache: 0,
inactive_anon: 0,
active_file: 0,
pgfault: 964,
inactive_file: 0,
total_pgpgin: 477,
},
max_usage: 1024,
failcnt: 0,
},
precpu_stats: {
cpu_usage: {
total_usage: 50,
percpu_usage: [50],
usage_in_usermode: 4,
usage_in_kernelmode: 4,
},
system_cpu_usage: 50,
online_cpus: 4,
throttling_data: {
periods: 0,
throttled_periods: 0,
throttled_time: 0,
},
},
cpu_stats: {
cpu_usage: {
total_usage: 60,
percpu_usage: [60],
usage_in_usermode: 4,
usage_in_kernelmode: 4,
},
system_cpu_usage: 95,
online_cpus: 4,
throttling_data: {
periods: 0,
throttled_periods: 0,
throttled_time: 0,
},
},
engineId: 'podman',
engineName: 'podman',
read: '',
preread: '',
num_procs: 1,
networks: {},
};
beforeAll(() => {
const containerStatsMock = vi.fn();
containerStatsMock.mockImplementation((engineId, id, stats) => {
return stats;
});
(window as any).getContainerStats = containerStatsMock;
(window as any).stopContainerStats = vi.fn();
});
test('Expect memory donut', async () => {
// render the component
const result = render(ContainerStatistics, { container: myContainer });
// update twice (needs two samples) and wait for update
result.component.updateStatistics(stats);
result.component.updateStatistics(stats);
await new Promise(resolve => setTimeout(resolve, 500));
const memValue = screen.getByText('256 B');
expect(memValue).toBeInTheDocument();
const memTooltip = screen.getByText('25% MEM usage');
expect(memTooltip).toBeInTheDocument();
const memArc = screen.getAllByTestId('arc')[1];
expect(memArc).toHaveClass('stroke-green-500');
});
test('Expect CPU donut', async () => {
// render the component
const result = render(ContainerStatistics, { container: myContainer });
// update twice (needs two samples) and wait for update
result.component.updateStatistics(stats);
result.component.updateStatistics(stats);
await new Promise(resolve => setTimeout(resolve, 500));
const cpuValue = screen.getByText('88.9%');
expect(cpuValue).toBeInTheDocument();
const cpuTooltip = screen.getByText('89% vCPUs usage');
expect(cpuTooltip).toBeInTheDocument();
const cpuArc = screen.getAllByTestId('arc')[0];
expect(cpuArc).toHaveClass('stroke-red-500');
});

View file

@ -23,7 +23,7 @@ let firstIteration = true;
let cpuUsage: string;
let memoryUsage: string;
async function updateStatistics(containerStats: ContainerStatsInfo) {
export async function updateStatistics(containerStats: ContainerStatsInfo) {
// we need enough data to compute the CPU usage
if (firstIteration) {
firstIteration = false;

View file

@ -30,7 +30,12 @@ $: tooltip = percent ? percent.toFixed(0) + '% ' + title + ' usage' : '';
<svg viewBox="-4 -4 {size + 8} {size + 8}" height="{size}" width="{size}">
<circle fill="none" class="stroke-charcoal-300" stroke-width="1" r="{size / 2}" cx="{size / 2}" cy="{size / 2}"
></circle>
<path fill="none" class="{stroke}" stroke-width="3.5" d="{describeArc(size / 2, (percent * 360) / 100)}"></path>
<path
fill="none"
class="{stroke}"
stroke-width="3.5"
d="{describeArc(size / 2, (percent * 360) / 100)}"
data-testid="arc"></path>
<text x="{size / 2}" y="38%" text-anchor="middle" font-size="{size / 5.5}" class="fill-gray-800">{title}</text>
<text
x="{size / 2}"