/* * * Copyright (C) 2022 Maxime Schmitt * * This file is part of Nvtop. * * Nvtop is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Nvtop is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with nvtop. If not, see . * */ #include #include #include #include extern "C" { #include "nvtop/interface.h" #include "nvtop/interface_layout_selection.h" } static std::ostream &operator<<(std::ostream &os, const struct window_position &win) { os << "Win (" << win.posX << ", " << win.posY << ")--(" << win.posX + win.sizeX << "," << win.posY + win.sizeY << ")"; return os; } namespace { // Returns true if the two windows overlap, false otherwise. bool window_position_overlap(struct window_position &w1, struct window_position &w2) { bool overlapX = w1.posX == w2.posX || (w1.posX < w2.posX && w1.posX + w1.sizeX - 1 >= w2.posX) || (w1.posX > w2.posX && w2.posX + w2.sizeX - 1 >= w1.posX); bool overlapY = w1.posY == w2.posY || (w1.posY < w2.posY && w1.posY + w1.sizeY - 1 >= w2.posY) || (w1.posY > w2.posY && w2.posY + w2.sizeY - 1 >= w1.posY); return overlapX && overlapY; } bool window_is_empty(const struct window_position &w1) { return w1.sizeX == 0 || w1.sizeY == 0; } // Check that the window is not empty // Returns true if the window is not empty bool check_non_empty_window(const struct window_position &w1) { EXPECT_GT(w1.sizeX, 0) << "Window " << w1 << " should not be empty"; EXPECT_GT(w1.sizeY, 0) << "Window " << w1 << " should not be empty"; return not window_is_empty(w1); } // Check that the none of the windows overlap any other bool check_no_windows_overlap(std::vector &windows) { bool has_overlap = false; for (unsigned winId = 0; winId < windows.size(); ++winId) { struct window_position ¤tWin = windows[winId]; for (unsigned winCompareId = winId + 1; winCompareId < windows.size(); ++winCompareId) { struct window_position &compareTo = windows[winCompareId]; bool overlaps = window_position_overlap(currentWin, compareTo); EXPECT_FALSE(overlaps) << "Between " << currentWin << " and " << compareTo; has_overlap = has_overlap || overlaps; } } return !has_overlap; } // Check that win does not extend past the container // Returns true if win is contained inside the container bool check_window_inside(const struct window_position &win, const struct window_position &container) { bool insideX = win.posX >= container.posX && win.posX + win.sizeX - 1 <= container.posX + container.sizeX - 1; EXPECT_TRUE(insideX) << win << " is not inside " << container; bool insideY = win.posY >= container.posY && win.posY + win.sizeY - 1 <= container.posY + container.sizeY - 1; EXPECT_TRUE(insideY) << win << " is not inside " << container; return insideX && insideY; } // Check that w1 is below w2 bool check_window_below(const struct window_position &w1, const struct window_position &w2) { EXPECT_GT(w1.posY, w2.posY + w2.sizeY - 1) << w1 << " is not below " << w2; return w1.posY > w2.posY + w2.sizeY - 1; } // Returns true if the layout is valid bool check_layout(struct window_position screen, std::vector &dev_pos, std::vector &plot_position, struct window_position process_position, struct window_position setup_position) { bool layout_valid = true; // Header // A particularity of the header is that it can be bigger than the screen for (auto const &dev_win : dev_pos) { bool valid = check_non_empty_window(dev_win); if (!valid) std::cout << "Error with header window: " << dev_win << std::endl; layout_valid = layout_valid && valid; } layout_valid = layout_valid && check_no_windows_overlap(dev_pos); // Plots for (auto const &plot_win : plot_position) { bool valid = check_non_empty_window(plot_win); if (!valid) std::cout << "Error with plot window: " << plot_win << std::endl; layout_valid = layout_valid && valid; } for (auto const &dev_win : dev_pos) { for (auto const &plot_win : plot_position) { layout_valid = layout_valid && check_window_below(plot_win, dev_win); layout_valid = layout_valid && check_window_inside(plot_win, screen); } } layout_valid = layout_valid && check_no_windows_overlap(plot_position); // Processes for (auto const &plot_win : plot_position) { layout_valid = layout_valid && check_window_below(process_position, plot_win); } if (not window_is_empty(process_position)) layout_valid = layout_valid && check_window_inside(process_position, screen); return layout_valid; } bool test_with_terminal_size(unsigned device_count, unsigned header_rows, unsigned header_cols, unsigned rows, unsigned cols) { struct window_position screen = {.posX = 0, .posY = 0, .sizeX = cols, .sizeY = rows}; plot_info_to_draw to_draw_default = plot_default_draw_info(); std::vector plot_display(device_count, to_draw_default); process_field_displayed proc_display = process_default_displayed_field(); unsigned num_plots = 0; std::vector dev_positions(device_count); std::vector plot_positions(MAX_CHARTS); struct window_position process_position; struct window_position setup_position; std::vector map_dev_to_plot(device_count); compute_sizes_from_layout(device_count, header_rows, header_cols, rows, cols, plot_display.data(), proc_display, dev_positions.data(), &num_plots, plot_positions.data(), map_dev_to_plot.data(), &process_position, &setup_position); plot_positions.resize(num_plots); return check_layout(screen, dev_positions, plot_positions, process_position, setup_position); } } // namespace TEST(InterfaceLayout, LayoutSelection_issue_147) { test_with_terminal_size(8, 3, 78, 26, 189); } TEST(InterfaceLayout, CheckEmptyProcessWindow) { unsigned device_count = 3, header_rows = 3, header_cols = 55, rows = 4, cols = 120; struct window_position screen = {.posX = 0, .posY = 0, .sizeX = cols, .sizeY = rows}; plot_info_to_draw to_draw_default = plot_default_draw_info(); std::vector plot_display(device_count, to_draw_default); process_field_displayed proc_display = process_default_displayed_field(); unsigned num_plots = 0; std::vector dev_positions(device_count); std::vector plot_positions(MAX_CHARTS); struct window_position process_position; struct window_position setup_position; std::vector map_dev_to_plot(device_count); compute_sizes_from_layout(device_count, header_rows, header_cols, rows, cols, plot_display.data(), proc_display, dev_positions.data(), &num_plots, plot_positions.data(), map_dev_to_plot.data(), &process_position, &setup_position); plot_positions.resize(num_plots); EXPECT_EQ(num_plots, 0); EXPECT_TRUE(window_is_empty(process_position)); } TEST(InterfaceLayout, FixInfiniteLoop) { unsigned device_count = 3, header_rows = 3, header_cols = 55, rows = 22, cols = 25; struct window_position screen = {.posX = 0, .posY = 0, .sizeX = cols, .sizeY = rows}; plot_info_to_draw to_draw_default = plot_default_draw_info(); std::vector plot_display(device_count, to_draw_default); process_field_displayed proc_display = process_default_displayed_field(); unsigned num_plots = 0; std::vector dev_positions(device_count); std::vector plot_positions(MAX_CHARTS); struct window_position process_position; struct window_position setup_position; std::vector map_dev_to_plot(device_count); compute_sizes_from_layout(device_count, header_rows, header_cols, rows, cols, plot_display.data(), proc_display, dev_positions.data(), &num_plots, plot_positions.data(), map_dev_to_plot.data(), &process_position, &setup_position); plot_positions.resize(num_plots); } TEST(InterfaceLayout, LayoutSelection_test_fail_case1) { test_with_terminal_size(32, 3, 55, 16, 1760); } #ifdef THOROUGH_TESTING TEST(InterfaceLayout, CheckManyTermSize) { const std::array dev_count_to_test = {0, 1, 2, 3, 6, 16, 32, 64}; const std::map extra_increment = {{0, 0}, {1, 0}, {2, 0}, {3, 0}, {6, 4}, {16, 6}, {32, 8}, {64, 17}}; for (unsigned dev_count : dev_count_to_test) { for (unsigned screen_rows = 1; screen_rows < 2048; screen_rows += 1 + extra_increment.at(dev_count)) { for (unsigned screen_cols = 1; screen_cols < 2048; screen_cols += 1 + extra_increment.at(dev_count)) { for (unsigned header_cols = 55; header_cols < 120; header_cols += 1 + extra_increment.at(dev_count)) { ASSERT_TRUE(test_with_terminal_size(dev_count, 3, header_cols, screen_rows, screen_cols)) << "Problem found with " << dev_count << " devices, (" << 3 << ", header " << header_cols << "), terminal size (" << screen_rows << ", " << screen_cols << ")"; } } } } } #endif // THOROUGH_TESTING