/* Puntos de recuperación nombrados (en memoria; la persistencia se prueba en test_io). */ #include "tgb_test.h" static tgb_context *g_ctx; static char g_root[200]; static tgb_document *new_doc(int w, int h) { tgb_document *d = NULL; CHECK_OK(tgb_document_create(g_ctx, w, h, 12, &d)); return d; } TEST(create_list_delete) { tgb_document *d = new_doc(16, 16); CHECK_EQ(tgb_recovery_point_count(d), 0); CHECK_OK(paint_px(d, 0, 0, 1, 1, RED)); uint64_t rev = tgb_document_revision(d); uint32_t a = 0, b = 0; CHECK_OK(tgb_recovery_point_create(d, "Base", &a)); CHECK(tgb_document_revision(d) != rev); /* hay algo nuevo que guardar */ CHECK_OK(paint_px(d, 0, 0, 2, 2, BLUE)); CHECK_OK(tgb_recovery_point_create(d, "Después del azul", &b)); CHECK_EQ(tgb_recovery_point_count(d), 2); tgb_recovery_point_info info; CHECK_OK(tgb_recovery_point_info_at(d, 0, &info)); CHECK(info.id == a && !strcmp(info.name, "Base") && info.compatible && info.created_unix > 1700000000); CHECK_STATUS(tgb_recovery_point_info_at(d, 5, &info), TGB_ERR_RANGE); CHECK_STATUS(tgb_recovery_point_create(d, "Base", NULL), TGB_ERR_EXISTS); CHECK_STATUS(tgb_recovery_point_create(d, "", NULL), TGB_ERR_INVALID_ARG); CHECK_STATUS(tgb_recovery_point_create(d, " ", NULL), TGB_ERR_INVALID_ARG); CHECK_STATUS(tgb_recovery_point_create(d, NULL, NULL), TGB_ERR_INVALID_ARG); CHECK_STATUS(tgb_recovery_point_create(d, "\xff\xfe", NULL), TGB_ERR_INVALID_ARG); CHECK_STATUS(tgb_recovery_point_create(d, "a\nb", NULL), TGB_ERR_INVALID_ARG); char longname[300]; memset(longname, 'x', sizeof longname - 1); longname[sizeof longname - 1] = 0; CHECK_STATUS(tgb_recovery_point_create(d, longname, NULL), TGB_ERR_LIMIT); CHECK_OK(tgb_recovery_point_delete(d, a)); CHECK_EQ(tgb_recovery_point_count(d), 1); CHECK_STATUS(tgb_recovery_point_delete(d, a), TGB_ERR_NOT_FOUND); CHECK_STATUS(tgb_recovery_point_restore(d, 9999), TGB_ERR_NOT_FOUND); CHECK_OK(tgb_edit_begin(d, tgb_frame_id(d, 0), tgb_layer_id(d, 0), "x")); CHECK_STATUS(tgb_recovery_point_create(d, "Durante edición", NULL), TGB_ERR_STATE); CHECK_OK(tgb_edit_cancel(d)); tgb_document_destroy(d); } TEST(restore_goes_back_and_keeps_safety_point) { tgb_document *d = new_doc(16, 16); CHECK_OK(paint_px(d, 0, 0, 1, 1, RED)); uint32_t base = 0; CHECK_OK(tgb_recovery_point_create(d, "Base", &base)); CHECK_OK(paint_px(d, 0, 0, 2, 2, BLUE)); CHECK_OK(tgb_frame_add(d, 1, NULL)); CHECK_OK(tgb_layer_add(d, 1, "Otra", NULL)); CHECK_EQ(tgb_frame_count(d), 2); CHECK_OK(tgb_recovery_point_restore(d, base)); CHECK_EQ(tgb_frame_count(d), 1); CHECK_EQ(tgb_layer_count(d), 1); CHECK(is_px(tgb_frame_cel(d, 0, 0), 1, 1, RED)); CHECK_EQ(px_at(d, 0, 0, 2, 2).a, 0); CHECK(!tgb_history_can_undo(d) && !tgb_history_can_redo(d)); /* el historial ordinario se vacía */ CHECK_EQ(tgb_recovery_point_count(d), 2); /* Base + punto de seguridad */ tgb_recovery_point_info info; CHECK_OK(tgb_recovery_point_info_at(d, 1, &info)); CHECK(strstr(info.name, "Antes de restaurar") != NULL && strstr(info.name, "Base") != NULL); /* volver al estado anterior a la restauración usando el punto automático */ CHECK_OK(tgb_recovery_point_restore(d, info.id)); CHECK_EQ(tgb_frame_count(d), 2); CHECK_EQ(tgb_layer_count(d), 2); CHECK(is_px(tgb_frame_cel(d, 0, 0), 2, 2, BLUE)); /* tras restaurar se sigue pudiendo editar y deshacer con normalidad */ CHECK_OK(paint_px(d, 0, 0, 9, 9, GREEN)); CHECK_OK(tgb_history_undo(d)); CHECK_EQ(px_at(d, 0, 0, 9, 9).a, 0); /* restaurar dos veces el mismo punto: nombres de seguridad únicos */ CHECK_OK(tgb_recovery_point_restore(d, base)); CHECK_OK(tgb_recovery_point_restore(d, base)); for (size_t i = 0; i < tgb_recovery_point_count(d); i++) for (size_t j = i + 1; j < tgb_recovery_point_count(d); j++) { tgb_recovery_point_info a, b; tgb_recovery_point_info_at(d, i, &a); tgb_recovery_point_info_at(d, j, &b); CHECK(strcmp(a.name, b.name) != 0); } CHECK(tgb_document_is_consistent(d)); tgb_document_destroy(d); } TEST(points_share_memory_copy_on_write) { tgb_document *d = new_doc(64, 64); CHECK_OK(paint_px(d, 0, 0, 1, 1, RED)); CHECK_OK(tgb_recovery_point_create(d, "P", NULL)); CHECK_EQ(tgb_recovery_points_extra_bytes(d), 0); /* comparte la celda: coste cero */ CHECK_EQ(tgb_canvas_refs(tgb_frame_cel(d, 0, 0)), 2); CHECK_OK(paint_px(d, 0, 0, 2, 2, BLUE)); /* editar separa la celda */ CHECK_EQ(tgb_recovery_points_extra_bytes(d), 64 * 64 * 4); /* ahora el punto retiene la versión vieja */ CHECK_OK(tgb_recovery_point_create(d, "Q", NULL)); /* otro punto de la misma versión: no duplica */ CHECK_OK(tgb_recovery_point_create(d, "R", NULL)); CHECK_EQ(tgb_recovery_points_extra_bytes(d), 64 * 64 * 4); tgb_document_destroy(d); } TEST(point_limit) { tgb_document *d = new_doc(8, 8); char name[32]; for (int i = 0; i < TGB_MAX_RECOVERY_POINTS; i++) { snprintf(name, sizeof name, "P%d", i); CHECK_OK(tgb_recovery_point_create(d, name, NULL)); } CHECK_STATUS(tgb_recovery_point_create(d, "Uno más", NULL), TGB_ERR_LIMIT); /* nunca borra puntos del usuario */ CHECK_EQ(tgb_recovery_point_count(d), TGB_MAX_RECOVERY_POINTS); tgb_document_destroy(d); } int main(void) { g_ctx = tgb_test_context(g_root, sizeof g_root); RUN(create_list_delete); RUN(restore_goes_back_and_keeps_safety_point); RUN(points_share_memory_copy_on_write); RUN(point_limit); tgb_context_destroy(g_ctx); tgb_test_rmtree(g_root); TEST_SUMMARY(); }