From 04b19c1a4d9beec62e67fde9a72ee1aa5246ffc4 Mon Sep 17 00:00:00 2001 From: Lucas Mariz Date: Sat, 12 Sep 2026 11:10:39 -0300 Subject: [PATCH 1/6] fix: harden legacy relation analysis --- .gitignore | 6 + Makefile | 33 + grafo.c | 763 +++++++++++------- grafo.h | 47 +- main.c | 10 +- makefile | 2 - .../fixtures/asymmetric_transitive/dados.txt | 4 + tests/fixtures/duplicate_element/dados.txt | 1 + tests/fixtures/duplicate_pair/dados.txt | 3 + tests/fixtures/empty_relation/dados.txt | 1 + tests/fixtures/empty_set/dados.txt | 1 + .../equivalence_multi_digit/dados.txt | 5 + tests/fixtures/invalid_count/dados.txt | 1 + tests/fixtures/legacy_sample/dados.txt | 19 + tests/fixtures/malformed_header/dados.txt | 1 + tests/fixtures/malformed_pair/dados.txt | 2 + tests/fixtures/missing_file/README.md | 1 + .../fixtures/non_transitive_closure/dados.txt | 3 + tests/fixtures/partial_order/dados.txt | 7 + .../symmetric_non_transitive/dados.txt | 5 + tests/fixtures/unknown_element/dados.txt | 2 + tests/run_tests.sh | 165 ++++ 22 files changed, 728 insertions(+), 354 deletions(-) create mode 100644 .gitignore create mode 100644 Makefile delete mode 100644 makefile create mode 100644 tests/fixtures/asymmetric_transitive/dados.txt create mode 100644 tests/fixtures/duplicate_element/dados.txt create mode 100644 tests/fixtures/duplicate_pair/dados.txt create mode 100644 tests/fixtures/empty_relation/dados.txt create mode 100644 tests/fixtures/empty_set/dados.txt create mode 100644 tests/fixtures/equivalence_multi_digit/dados.txt create mode 100644 tests/fixtures/invalid_count/dados.txt create mode 100644 tests/fixtures/legacy_sample/dados.txt create mode 100644 tests/fixtures/malformed_header/dados.txt create mode 100644 tests/fixtures/malformed_pair/dados.txt create mode 100644 tests/fixtures/missing_file/README.md create mode 100644 tests/fixtures/non_transitive_closure/dados.txt create mode 100644 tests/fixtures/partial_order/dados.txt create mode 100644 tests/fixtures/symmetric_non_transitive/dados.txt create mode 100644 tests/fixtures/unknown_element/dados.txt create mode 100755 tests/run_tests.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3495b9d --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/a.out +/a.out-sanitize +*.o +*.gcda +*.gcno +*.gcov diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1c463f0 --- /dev/null +++ b/Makefile @@ -0,0 +1,33 @@ +CC ?= cc +CPPFLAGS ?= +CFLAGS ?= -O2 +LDFLAGS ?= +LDLIBS ?= + +STANDARD_FLAGS := -std=c17 +WARNING_FLAGS := -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Werror +SANITIZER_FLAGS := -g -O1 -fno-omit-frame-pointer -fsanitize=address,undefined + +TARGET := a.out +SANITIZER_TARGET := a.out-sanitize +SOURCES := main.c grafo.c +HEADERS := grafo.h + +.PHONY: all test sanitize clean + +all: $(TARGET) + +$(TARGET): $(SOURCES) $(HEADERS) + $(CC) $(CPPFLAGS) $(STANDARD_FLAGS) $(WARNING_FLAGS) $(CFLAGS) $(SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ + +$(SANITIZER_TARGET): $(SOURCES) $(HEADERS) + $(CC) $(CPPFLAGS) $(STANDARD_FLAGS) $(WARNING_FLAGS) $(SANITIZER_FLAGS) $(SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ + +test: $(TARGET) + ./tests/run_tests.sh ./$(TARGET) + +sanitize: $(SANITIZER_TARGET) + ASAN_OPTIONS=detect_leaks=$${ASAN_DETECT_LEAKS:-0} ./tests/run_tests.sh ./$(SANITIZER_TARGET) + +clean: + rm -f $(TARGET) $(SANITIZER_TARGET) diff --git a/grafo.c b/grafo.c index 10180ce..5b83226 100644 --- a/grafo.c +++ b/grafo.c @@ -1,18 +1,25 @@ /* Trabalho desenvolvido por Lucas Paulo Martins Mariz - Técnico em informática formado pelo Coltec/UFMG E Aluno de Ciência da Computação UFMG */ +#include +#include +#include +#include #include #include #include -#include "grafo.h" + +#include "grafo.h" + +#define MAX_VERTICES 50 +#define INPUT_BUFFER_SIZE 4096 struct grafo { - int **matriz_adjacencia; // Matriz de adjacencias que representa o grafo - int n_vertices; // Número total de vertices que o grafo terá - int elementos[50]; // Nomes dos vértices fornecidos pelo usuário - int n_ligacoes; // Número total de ligações que o grafo fará + int **matriz_adjacencia; + int n_vertices; + int elementos[MAX_VERTICES]; + int n_ligacoes; - // Propriedades: 1 -> V | 0 -> F int propriedade_reflexiva; int propriedade_irreflexiva; int propriedade_simetrica; @@ -24,445 +31,577 @@ struct grafo { int relacao_ordem_parcial; }; +static int proximo_inteiro(const char **cursor, int *valor) { + char *fim; + long numero; -Grafo* cria_grafo(int *excessao_zero) { - Grafo *gr; - int aux; + while (isspace((unsigned char)**cursor)) { + (*cursor)++; + } - gr = preenche_grafo(&aux); - *excessao_zero = aux; - return gr; + if (**cursor == '\0') { + return 0; + } + + errno = 0; + numero = strtol(*cursor, &fim, 10); + if (fim == *cursor || errno == ERANGE || numero < INT_MIN || numero > INT_MAX) { + return 0; + } + + *valor = (int)numero; + *cursor = fim; + return 1; +} + +static int contem_apenas_espacos(const char *cursor) { + while (isspace((unsigned char)*cursor)) { + cursor++; + } + return *cursor == '\0'; +} + +static int linha_excedeu_buffer(const char *linha, FILE *arquivo) { + return strchr(linha, '\n') == NULL && !feof(arquivo); +} + +static int **calcula_fecho_transitivo(const Grafo *gr) { + int i, j, k; + int **fecho = aloca_matriz(gr->n_vertices); + + if (gr->n_vertices > 0 && fecho == NULL) { + return NULL; + } + + for (i = 0; i < gr->n_vertices; i++) { + for (j = 0; j < gr->n_vertices; j++) { + fecho[i][j] = gr->matriz_adjacencia[i][j]; + } + } + + for (k = 0; k < gr->n_vertices; k++) { + for (i = 0; i < gr->n_vertices; i++) { + if (fecho[i][k] == 0) { + continue; + } + for (j = 0; j < gr->n_vertices; j++) { + if (fecho[k][j] == 1) { + fecho[i][j] = 1; + } + } + } + } + + return fecho; +} + +Grafo *cria_grafo(int *excessao_zero) { + if (excessao_zero == NULL) { + return NULL; + } + return preenche_grafo(excessao_zero); } -void libera_matriz(int **m, int tam) { //Função para liberar o espaço alocado na memória +void libera_matriz(int **m, int tam) { int i; - for(i=0;ielementos[i] = -1; - - if(arq == NULL) - printf("Erro, nao foi possivel abrir o arquivo\n"); - else { - n_vertices[0] = fgetc(arq); //Pega o primeiro caractere do arquivo que representa o número de elementos - int n_vertices_inteiro = atoi(n_vertices); //Transformação de char para inteiro - gr->n_vertices = n_vertices_inteiro; - - // Inicialização das propriedades - gr->propriedade_reflexiva = 1; - gr->propriedade_irreflexiva = 1; - gr->propriedade_simetrica = 1; - gr->propriedade_anti_simetrica = 1; - gr->propriedade_assimetrica = 1; - gr->propriedade_transitiva = 1; - gr->relacao_equivalencia = 1; - gr->relacao_ordem_parcial = 1; - - if(gr->n_vertices == 0) // Todas propriedades são verdadeiras - *excessao_zero = 1; - else { - *excessao_zero = 0; - // Alocação da Matriz de adjacencias quadrada: - - gr->matriz_adjacencia = (int**) calloc(gr->n_vertices, sizeof(int*)); - for(i=0;in_vertices;i++) - gr->matriz_adjacencia[i] = (int*) calloc(gr->n_vertices, sizeof(int)); - - char info[50]; - int linha=0; - while(fgets(info, sizeof(info), arq)!=NULL) { - if(linha) { //Se não estiver na primeira linha do arquivo temos as ligações - //Lendo dados referentes aos relacionamentos feitos entre as arestas do grafo - int n1,n2; - char aux1[1], aux2[1]; - aux1[0] = info[0]; - aux2[0] = info[2]; - //Conversões de char para inteiro - n1 = aux1[0] - '0'; - n2 = aux2[0] - '0'; - - insere_aresta(gr, n1, n2); - } - else { //Se estivermos na primeira linha do arquivo temos os nomes dados as arestas a partir da segunda posição da linha, pois na primeira tem a quantidade de arestas - int cont = 0; - for(i=1;ielementos[cont] = nome; - cont++; - } - } - linha++; + Grafo *gr; + char info[INPUT_BUFFER_SIZE]; + const char *cursor; + int i, j, n_vertices; + size_t numero_linha = 1; + + if (excessao_zero == NULL) { + return NULL; + } + *excessao_zero = 0; + + arq = fopen("dados.txt", "r"); + if (arq == NULL) { + fprintf(stderr, "Erro, nao foi possivel abrir o arquivo dados.txt\n"); + return NULL; + } + + gr = calloc(1, sizeof(*gr)); + if (gr == NULL) { + fprintf(stderr, "Erro, nao foi possivel alocar o grafo\n"); + fclose(arq); + return NULL; + } + + for (i = 0; i < MAX_VERTICES; i++) { + gr->elementos[i] = -1; + } + + if (fgets(info, sizeof(info), arq) == NULL || linha_excedeu_buffer(info, arq)) { + fprintf(stderr, "Erro de entrada na linha 1: cabecalho ausente ou muito longo\n"); + goto erro; + } + + cursor = info; + if (!proximo_inteiro(&cursor, &n_vertices) || n_vertices < 0 || n_vertices > MAX_VERTICES) { + fprintf(stderr, "Erro de entrada na linha 1: numero de elementos invalido\n"); + goto erro; + } + gr->n_vertices = n_vertices; + + for (i = 0; i < gr->n_vertices; i++) { + if (!proximo_inteiro(&cursor, &gr->elementos[i])) { + fprintf(stderr, "Erro de entrada na linha 1: quantidade de elementos incorreta\n"); + goto erro; + } + for (j = 0; j < i; j++) { + if (gr->elementos[j] == gr->elementos[i]) { + fprintf(stderr, "Erro de entrada na linha 1: elementos duplicados\n"); + goto erro; } } - fclose(arq); - } - return gr; + } + + if (!contem_apenas_espacos(cursor)) { + fprintf(stderr, "Erro de entrada na linha 1: quantidade de elementos incorreta\n"); + goto erro; + } + + if (gr->n_vertices > 0) { + gr->matriz_adjacencia = aloca_matriz(gr->n_vertices); + if (gr->matriz_adjacencia == NULL) { + fprintf(stderr, "Erro, nao foi possivel alocar a matriz de adjacencia\n"); + goto erro; + } + } + + while (fgets(info, sizeof(info), arq) != NULL) { + int origem, destino; + + numero_linha++; + if (linha_excedeu_buffer(info, arq)) { + fprintf(stderr, "Erro de entrada na linha %zu: linha muito longa\n", numero_linha); + goto erro; + } + + cursor = info; + while (isspace((unsigned char)*cursor)) { + cursor++; + } + if (*cursor == '\0') { + continue; + } + + if (!proximo_inteiro(&cursor, &origem) || !proximo_inteiro(&cursor, &destino) || + !contem_apenas_espacos(cursor)) { + fprintf(stderr, "Erro de entrada na linha %zu: par ordenado invalido\n", numero_linha); + goto erro; + } + + if (!insere_aresta(gr, origem, destino)) { + fprintf(stderr, "Erro de entrada na linha %zu: elemento desconhecido\n", numero_linha); + goto erro; + } + } + + if (ferror(arq)) { + fprintf(stderr, "Erro durante a leitura de dados.txt\n"); + goto erro; + } + + gr->propriedade_reflexiva = 1; + gr->propriedade_irreflexiva = 1; + gr->propriedade_simetrica = 1; + gr->propriedade_anti_simetrica = 1; + gr->propriedade_assimetrica = 1; + gr->propriedade_transitiva = 1; + gr->relacao_equivalencia = 1; + gr->relacao_ordem_parcial = 1; + *excessao_zero = gr->n_vertices == 0; + + fclose(arq); + return gr; + +erro: + fclose(arq); + libera_matriz(gr->matriz_adjacencia, gr->n_vertices); + free(gr); + return NULL; } -void insere_aresta(Grafo *gr, int orig, int dest) { //Função para inserir um relacionamento - int i, nova_origem, novo_destino; -/* -Realização da tradução entre o nome colocado pelo usuário e a verdadeira posição que esse númemro ocupa no vetor, -de forma que os calculos ocorram de forma transparente para o usuário. -*/ - for(i=0;gr->elementos[i]!=-1;i++) { - if(gr->elementos[i] == orig) - nova_origem=i; - if(gr->elementos[i] == dest) - novo_destino=i; +int insere_aresta(Grafo *gr, int orig, int dest) { + int i; + int nova_origem = -1; + int novo_destino = -1; + + if (gr == NULL) { + return 0; + } + + for (i = 0; i < gr->n_vertices; i++) { + if (gr->elementos[i] == orig) { + nova_origem = i; + } + if (gr->elementos[i] == dest) { + novo_destino = i; + } } - gr->matriz_adjacencia[nova_origem][novo_destino] = 1; - gr->n_ligacoes++; //O número total de relacionamentos do grafo aumenta. + if (nova_origem < 0 || novo_destino < 0) { + return 0; + } + + if (gr->matriz_adjacencia[nova_origem][novo_destino] == 0) { + gr->matriz_adjacencia[nova_origem][novo_destino] = 1; + gr->n_ligacoes++; + } + return 1; } void imprime_matriz(Grafo *gr) { - int i, j; - - printf("\n\t"); - for(i=0;in_vertices;i++) - printf("%d ", gr->elementos[i]); - - printf("\n\n\n"); - for(i=0;in_vertices;i++) { - printf("%d\t", gr->elementos[i]); - for(j=0;jn_vertices;j++) { - printf("%d ", gr->matriz_adjacencia[i][j]); - } - printf("\n"); - } - printf("\n\n"); + int i, j; + + printf("\n\t"); + for (i = 0; i < gr->n_vertices; i++) { + printf("%d ", gr->elementos[i]); + } + + printf("\n\n\n"); + for (i = 0; i < gr->n_vertices; i++) { + printf("%d\t", gr->elementos[i]); + for (j = 0; j < gr->n_vertices; j++) { + printf("%d ", gr->matriz_adjacencia[i][j]); + } + printf("\n"); + } + printf("\n\n"); } -int** aloca_matriz(int tam) { - int i,j; - int **m = (int**) calloc(tam, sizeof(int*)); - - for(i=0;in_vertices); + int i; - for(i=0;in_vertices;i++) - if(gr->matriz_adjacencia[i][i] == 0) { - matriz_adjacencia_auxiliar[i][i] = 1; + gr->propriedade_reflexiva = 1; + for (i = 0; i < gr->n_vertices; i++) { + if (gr->matriz_adjacencia[i][i] == 0) { gr->propriedade_reflexiva = 0; } + } - if(gr->propriedade_reflexiva == 1) + if (gr->propriedade_reflexiva == 1) { printf("1. Reflexiva: V\n"); - else { - printf("1. Reflexiva: F\n"); - for(i=0;in_vertices;i++) { - if(matriz_adjacencia_auxiliar[i][i] == 1) - printf("(%d,%d); ", gr->elementos[i], gr->elementos[i]); + return; + } + + printf("1. Reflexiva: F\n"); + for (i = 0; i < gr->n_vertices; i++) { + if (gr->matriz_adjacencia[i][i] == 0) { + printf("(%d,%d); ", gr->elementos[i], gr->elementos[i]); } - printf("\n"); } + printf("\n"); } void irreflexiva(Grafo *gr) { - int i, **matriz_adjacencia_auxiliar = aloca_matriz(gr->n_vertices); + int i; - for(i=0;in_vertices;i++) - if(gr->matriz_adjacencia[i][i] == 1) { - matriz_adjacencia_auxiliar[i][i] = 1; + gr->propriedade_irreflexiva = 1; + for (i = 0; i < gr->n_vertices; i++) { + if (gr->matriz_adjacencia[i][i] == 1) { gr->propriedade_irreflexiva = 0; } + } - if(gr->propriedade_irreflexiva == 1) + if (gr->propriedade_irreflexiva == 1) { printf("2. Irreflexiva: V\n"); - else { - printf("2. Irreflexiva: F\n"); - for(i=0;in_vertices;i++) { - if(matriz_adjacencia_auxiliar[i][i] == 1) - printf("(%d,%d); ", gr->elementos[i], gr->elementos[i]); + return; + } + + printf("2. Irreflexiva: F\n"); + for (i = 0; i < gr->n_vertices; i++) { + if (gr->matriz_adjacencia[i][i] == 1) { + printf("(%d,%d); ", gr->elementos[i], gr->elementos[i]); } - printf("\n"); } + printf("\n"); } void simetrica(Grafo *gr) { - int i, j, **matriz_adjacencia_auxiliar = aloca_matriz(gr->n_vertices); - - for(i=0;in_vertices;i++) - for(j=0;jn_vertices;j++) - if(gr->matriz_adjacencia[i][j] == 1) - if(gr->matriz_adjacencia[j][i] == 0) { - gr->propriedade_simetrica = 0; - matriz_adjacencia_auxiliar[i][j] = 1; - } + int i, j; + + gr->propriedade_simetrica = 1; + for (i = 0; i < gr->n_vertices; i++) { + for (j = 0; j < gr->n_vertices; j++) { + if (gr->matriz_adjacencia[i][j] == 1 && gr->matriz_adjacencia[j][i] == 0) { + gr->propriedade_simetrica = 0; + } + } + } - if(gr->propriedade_simetrica == 1) + if (gr->propriedade_simetrica == 1) { printf("3. Simetrica: V\n"); - else { - printf("3. Simetrica: F\n"); - for(i=0;in_vertices;i++) { - for(j=0;jn_vertices;j++) { - if(matriz_adjacencia_auxiliar[i][j] == 1) - printf("(%d,%d) e (%d,%d); ", gr->elementos[i], gr->elementos[j], gr->elementos[j], gr->elementos[i]); + return; + } + + printf("3. Simetrica: F\n"); + for (i = 0; i < gr->n_vertices; i++) { + for (j = 0; j < gr->n_vertices; j++) { + if (gr->matriz_adjacencia[i][j] == 1 && gr->matriz_adjacencia[j][i] == 0) { + printf("(%d,%d) e (%d,%d); ", gr->elementos[i], gr->elementos[j], + gr->elementos[j], gr->elementos[i]); } } - printf("\n"); } + printf("\n"); } void anti_simetrica(Grafo *gr) { - int i, j, **matriz_adjacencia_auxiliar = aloca_matriz(gr->n_vertices); - - for(i=0;in_vertices;i++) - for(j=0;jn_vertices;j++) - if(gr->matriz_adjacencia[i][j] == 1) - if(gr->matriz_adjacencia[j][i] == 1) { - if(i != j) { - gr->propriedade_anti_simetrica = 0; - matriz_adjacencia_auxiliar[i][j] = 1; - } - } + int i, j; + + gr->propriedade_anti_simetrica = 1; + for (i = 0; i < gr->n_vertices; i++) { + for (j = i + 1; j < gr->n_vertices; j++) { + if (gr->matriz_adjacencia[i][j] == 1 && gr->matriz_adjacencia[j][i] == 1) { + gr->propriedade_anti_simetrica = 0; + } + } + } - if(gr->propriedade_anti_simetrica == 1) + if (gr->propriedade_anti_simetrica == 1) { printf("4. Anti-simetrica: V\n"); - else { - printf("4. Anti-simetrica: F\n"); - for(i=0;in_vertices;i++) { - for(j=0;jn_vertices;j++) { - if(matriz_adjacencia_auxiliar[i][j] == 1) { - printf("(%d,%d) e (%d,%d); ", gr->elementos[i], gr->elementos[j], gr->elementos[j], gr->elementos[i]); - matriz_adjacencia_auxiliar[i][j] = 0; - matriz_adjacencia_auxiliar[j][i] = 0; - } + return; + } + + printf("4. Anti-simetrica: F\n"); + for (i = 0; i < gr->n_vertices; i++) { + for (j = i + 1; j < gr->n_vertices; j++) { + if (gr->matriz_adjacencia[i][j] == 1 && gr->matriz_adjacencia[j][i] == 1) { + printf("(%d,%d) e (%d,%d); ", gr->elementos[i], gr->elementos[j], + gr->elementos[j], gr->elementos[i]); } } - printf("\n"); - } + } + printf("\n"); } void assimetrica(Grafo *gr) { int i, j; - - for(i=0;in_vertices;i++) - for(j=0;jn_vertices;j++) - if(gr->matriz_adjacencia[i][j] == 1) - if(gr->matriz_adjacencia[j][i] == 1) - gr->propriedade_assimetrica = 0; - - if(gr->propriedade_assimetrica == 1) + + gr->propriedade_assimetrica = 1; + for (i = 0; i < gr->n_vertices; i++) { + for (j = 0; j < gr->n_vertices; j++) { + if (gr->matriz_adjacencia[i][j] == 1 && gr->matriz_adjacencia[j][i] == 1) { + gr->propriedade_assimetrica = 0; + } + } + } + + if (gr->propriedade_assimetrica == 1) { printf("5. Asimetrica: V\n"); - else - printf("5. Asimetrica: F\n"); + } else { + printf("5. Asimetrica: F\n"); + } } -void transitiva(Grafo *gr) { - int i, j, k, **matriz_adjacencia_auxiliar = aloca_matriz(gr->n_vertices); - Grafo *auxiliar = cria_grafo(&i); - - for(i=0;in_vertices;i++) { - for(j=0;jn_vertices;j++) { - if(auxiliar->matriz_adjacencia[i][j] == 1) { // Existe (x,y) - for(k=0;kn_vertices;k++) { - if(auxiliar->matriz_adjacencia[j][k] == 1) { // Existe (y,z) - if(auxiliar->matriz_adjacencia[i][k] == 0) { // Não existe (x,z) - gr->propriedade_transitiva = 0; - matriz_adjacencia_auxiliar[i][k] = 1; - auxiliar->matriz_adjacencia[i][k] = 1; - i=0; - j=0; - } - } - } +int transitiva(Grafo *gr) { + int i, j; + int **fecho = calcula_fecho_transitivo(gr); + + if (gr->n_vertices > 0 && fecho == NULL) { + fprintf(stderr, "Erro, nao foi possivel calcular o fecho transitivo\n"); + return -1; + } + + gr->propriedade_transitiva = 1; + for (i = 0; i < gr->n_vertices; i++) { + for (j = 0; j < gr->n_vertices; j++) { + if (fecho[i][j] == 1 && gr->matriz_adjacencia[i][j] == 0) { + gr->propriedade_transitiva = 0; } } } - if(gr->propriedade_transitiva == 1) + if (gr->propriedade_transitiva == 1) { printf("6. Transitiva: V\n"); - else { + } else { printf("6. Transitiva: F\n"); - for(i=0;in_vertices;i++) { - for(j=0;jn_vertices;j++) { - if(matriz_adjacencia_auxiliar[i][j] == 1) + for (i = 0; i < gr->n_vertices; i++) { + for (j = 0; j < gr->n_vertices; j++) { + if (fecho[i][j] == 1 && gr->matriz_adjacencia[i][j] == 0) { printf("(%d,%d); ", gr->elementos[i], gr->elementos[j]); + } } } printf("\n"); } - libera_matriz(auxiliar->matriz_adjacencia, auxiliar->n_vertices); - free(auxiliar); + + libera_matriz(fecho, gr->n_vertices); + return 0; } void equivalencia(Grafo *gr) { - if(gr->propriedade_reflexiva == 1 && gr->propriedade_simetrica == 1 && gr->propriedade_transitiva == 1) { - gr->relacao_equivalencia = 1; - printf("Relaçao de equivalencia: V\n"); - } - else { - gr->relacao_equivalencia = 0; - printf("Relaçao de equivalencia: F\n"); - } + gr->relacao_equivalencia = gr->propriedade_reflexiva == 1 && + gr->propriedade_simetrica == 1 && + gr->propriedade_transitiva == 1; + printf("Relaçao de equivalencia: %c\n", gr->relacao_equivalencia ? 'V' : 'F'); } void ordem_parcial(Grafo *gr) { - if(gr->propriedade_reflexiva == 1 && gr->propriedade_anti_simetrica == 1 && gr->propriedade_transitiva == 1) { - gr->relacao_ordem_parcial = 1; - printf("Relaçao de ordem parcial: V\n"); - } - else { - gr->relacao_ordem_parcial = 0; - printf("Relaçao de ordem parcial: F\n"); - } + gr->relacao_ordem_parcial = gr->propriedade_reflexiva == 1 && + gr->propriedade_anti_simetrica == 1 && + gr->propriedade_transitiva == 1; + printf("Relaçao de ordem parcial: %c\n", gr->relacao_ordem_parcial ? 'V' : 'F'); } -void fecho_padrao(Grafo *gr) { // Imprimir todas as relações já existentes caso seja reflexiva, simétrica e/ou transitiva - int i, j, controle_virgula = 0; +void fecho_padrao(Grafo *gr) { + int i, j; + bool primeiro = true; - for(i=0;in_vertices;i++) { - for(j=0;jn_vertices;j++) { - if(controle_virgula == 1) { - printf(","); - controle_virgula = 0; - } - if(gr->matriz_adjacencia[i][j] == 1) { + for (i = 0; i < gr->n_vertices; i++) { + for (j = 0; j < gr->n_vertices; j++) { + if (gr->matriz_adjacencia[i][j] == 1) { + if (!primeiro) { + printf(","); + } printf("(%d,%d)", gr->elementos[i], gr->elementos[j]); - controle_virgula = 1; + primeiro = false; } } } } void fecho_reflexivo(Grafo *gr) { + int i; + bool precisa_virgula = gr->n_ligacoes > 0; + printf("Fecho reflexivo da relaçao = {"); fecho_padrao(gr); - if(gr->propriedade_reflexiva == 0) { - int i, **matriz_adjacencia_auxiliar = aloca_matriz(gr->n_vertices), controle_virgula = 1; - for(i=0;in_vertices;i++) - if(gr->matriz_adjacencia[i][i] == 0) - matriz_adjacencia_auxiliar[i][i] = 1; - - for(i=0;in_vertices;i++) { - if(matriz_adjacencia_auxiliar[i][i] == 1) { - if(controle_virgula) - printf(","); - controle_virgula = 0; - controle_virgula = 1; - printf("(%d,%d)", gr->elementos[i], gr->elementos[i]); + for (i = 0; i < gr->n_vertices; i++) { + if (gr->matriz_adjacencia[i][i] == 0) { + if (precisa_virgula) { + printf(","); } + printf("(%d,%d)", gr->elementos[i], gr->elementos[i]); + precisa_virgula = true; } } printf("}\n"); } void fecho_simetrico(Grafo *gr) { + int i, j; + bool precisa_virgula = gr->n_ligacoes > 0; + printf("Fecho simetrico da relaçao = {"); fecho_padrao(gr); - if(gr->propriedade_simetrica == 0) { - int i, j, **matriz_adjacencia_auxiliar = aloca_matriz(gr->n_vertices), controle_virgula = 1; - for(i=0;in_vertices;i++) - for(j=0;jn_vertices;j++) - if(gr->matriz_adjacencia[i][j] == 1) - if(gr->matriz_adjacencia[j][i] == 0) { - gr->propriedade_simetrica = 0; - matriz_adjacencia_auxiliar[i][j] = 1; - } - - for(i=0;in_vertices;i++) { - for(j=0;jn_vertices;j++) { - if(matriz_adjacencia_auxiliar[i][j] == 1) { - if(controle_virgula == 1) - printf(","); - controle_virgula = 0; - printf("(%d,%d)", gr->elementos[j], gr->elementos[i]); - controle_virgula = 1; + for (i = 0; i < gr->n_vertices; i++) { + for (j = 0; j < gr->n_vertices; j++) { + if (gr->matriz_adjacencia[i][j] == 1 && gr->matriz_adjacencia[j][i] == 0) { + if (precisa_virgula) { + printf(","); } + printf("(%d,%d)", gr->elementos[j], gr->elementos[i]); + precisa_virgula = true; } } } printf("}\n"); } -void fecho_transitivo(Grafo *gr) { +int fecho_transitivo(Grafo *gr) { + int i, j; + bool precisa_virgula = gr->n_ligacoes > 0; + int **fecho = calcula_fecho_transitivo(gr); + + if (gr->n_vertices > 0 && fecho == NULL) { + fprintf(stderr, "Erro, nao foi possivel calcular o fecho transitivo\n"); + return -1; + } + printf("Fecho transitivo da relaçao = {"); fecho_padrao(gr); - int i, j, k, **matriz_adjacencia_auxiliar = aloca_matriz(gr->n_vertices), controle_virgula = 1; - Grafo *auxiliar = cria_grafo(&i); - if(gr->propriedade_transitiva == 0) { - - for(i=0;in_vertices;i++) { - for(j=0;jn_vertices;j++) { - if(auxiliar->matriz_adjacencia[i][j] == 1) { // Existe (x,y) - for(k=0;kn_vertices;k++) { - if(auxiliar->matriz_adjacencia[j][k] == 1) { // Existe (y,z) - if(auxiliar->matriz_adjacencia[i][k] == 0) { // Não existe (x,z) - gr->propriedade_transitiva = 0; - matriz_adjacencia_auxiliar[i][k] = 1; - auxiliar->matriz_adjacencia[i][k] = 1; - i=0; - j=0; - } - } - } - } - } - } - - for(i=0;in_vertices;i++) { - for(j=0;jn_vertices;j++) { - if(matriz_adjacencia_auxiliar[i][j] == 1){ - if(controle_virgula == 1) - printf(","); - controle_virgula = 0; - printf("(%d,%d)", gr->elementos[i], gr->elementos[j]); + for (i = 0; i < gr->n_vertices; i++) { + for (j = 0; j < gr->n_vertices; j++) { + if (fecho[i][j] == 1 && gr->matriz_adjacencia[i][j] == 0) { + if (precisa_virgula) { + printf(","); } + printf("(%d,%d)", gr->elementos[i], gr->elementos[j]); + precisa_virgula = true; } } } printf("}\n"); - libera_matriz(auxiliar->matriz_adjacencia, auxiliar->n_vertices); - free(auxiliar); + + libera_matriz(fecho, gr->n_vertices); + return 0; } -void propriedades(Grafo *gr) { +int propriedades(Grafo *gr) { printf("\n\nPropriedades:\n\n"); imprime_matriz(gr); - reflexiva(gr); + reflexiva(gr); irreflexiva(gr); simetrica(gr); anti_simetrica(gr); assimetrica(gr); - transitiva(gr); + if (transitiva(gr) != 0) { + return -1; + } printf("\n"); equivalencia(gr); - ordem_parcial(gr); - // Fechos: + ordem_parcial(gr); printf("\n"); fecho_reflexivo(gr); fecho_simetrico(gr); - fecho_transitivo(gr); + if (fecho_transitivo(gr) != 0) { + return -1; + } printf("\n"); + return 0; } -void inicializar() { - int excessao_zero; // Caso seja um grafo sem nenhum vértice +int inicializar(void) { + int excessao_zero; Grafo *gr = cria_grafo(&excessao_zero); - propriedades(gr); + int resultado; + + if (gr == NULL) { + return EXIT_FAILURE; + } + + resultado = propriedades(gr); libera_matriz(gr->matriz_adjacencia, gr->n_vertices); free(gr); + return resultado == 0 ? EXIT_SUCCESS : EXIT_FAILURE; } diff --git a/grafo.h b/grafo.h index 7e3ca7f..f9d9c27 100644 --- a/grafo.h +++ b/grafo.h @@ -1,46 +1,27 @@ -/* -Trabalho desenvolvido por Lucas Paulo Martins Mariz - Técnico em informática formado pelo Coltec/UFMG E Aluno de Ciência da Computação UFMG -*/ -//Declaração de funções: +#ifndef GRAFO_H +#define GRAFO_H + typedef struct grafo Grafo; -// #1) -Grafo* cria_grafo(int *excessao_zero); -// #2) -Grafo* preenche_grafo(int *excessao_zero); -// #3) -void insere_aresta(Grafo *gr, int orig, int dest); -// #4) +Grafo *cria_grafo(int *excessao_zero); +Grafo *preenche_grafo(int *excessao_zero); +int insere_aresta(Grafo *gr, int orig, int dest); void imprime_matriz(Grafo *gr); -// #5) -void inicializar(); -// #6) -void propriedades(Grafo *gr); -// #7) +int inicializar(void); +int propriedades(Grafo *gr); void libera_matriz(int **m, int tam); -// #8) -int** aloca_matriz(int tam); -// #9) +int **aloca_matriz(int tam); void reflexiva(Grafo *gr); -// #10) void irreflexiva(Grafo *gr); -// #11) void simetrica(Grafo *gr); -// #12) void anti_simetrica(Grafo *gr); -// #13) void assimetrica(Grafo *gr); -// #14) -void transitiva(Grafo *gr); -// #15) +int transitiva(Grafo *gr); void equivalencia(Grafo *gr); -// #16) void ordem_parcial(Grafo *gr); -// #17) void fecho_reflexivo(Grafo *gr); -// #18) void fecho_simetrico(Grafo *gr); -// #19) -void fecho_transitivo(Grafo *gr); -// #20) -void fecho_padrao(Grafo *gr); \ No newline at end of file +int fecho_transitivo(Grafo *gr); +void fecho_padrao(Grafo *gr); + +#endif diff --git a/main.c b/main.c index b2131bf..ea68ab5 100644 --- a/main.c +++ b/main.c @@ -1,12 +1,8 @@ /* Trabalho desenvolvido por Lucas Paulo Martins Mariz - Técnico em informática formado pelo Coltec/UFMG E Aluno de Ciência da Computação UFMG */ -#include -#include -#include #include "grafo.h" -int main() { - inicializar(); - return 0; -} \ No newline at end of file +int main(void) { + return inicializar(); +} diff --git a/makefile b/makefile deleted file mode 100644 index 19dc88c..0000000 --- a/makefile +++ /dev/null @@ -1,2 +0,0 @@ -all: - gcc grafo.c main.c \ No newline at end of file diff --git a/tests/fixtures/asymmetric_transitive/dados.txt b/tests/fixtures/asymmetric_transitive/dados.txt new file mode 100644 index 0000000..4ca63e7 --- /dev/null +++ b/tests/fixtures/asymmetric_transitive/dados.txt @@ -0,0 +1,4 @@ +3 1 2 3 +1 2 +2 3 +1 3 diff --git a/tests/fixtures/duplicate_element/dados.txt b/tests/fixtures/duplicate_element/dados.txt new file mode 100644 index 0000000..33821be --- /dev/null +++ b/tests/fixtures/duplicate_element/dados.txt @@ -0,0 +1 @@ +2 1 1 diff --git a/tests/fixtures/duplicate_pair/dados.txt b/tests/fixtures/duplicate_pair/dados.txt new file mode 100644 index 0000000..e1d1054 --- /dev/null +++ b/tests/fixtures/duplicate_pair/dados.txt @@ -0,0 +1,3 @@ +2 10 20 +10 20 +10 20 diff --git a/tests/fixtures/empty_relation/dados.txt b/tests/fixtures/empty_relation/dados.txt new file mode 100644 index 0000000..c3b04cc --- /dev/null +++ b/tests/fixtures/empty_relation/dados.txt @@ -0,0 +1 @@ +2 1 2 diff --git a/tests/fixtures/empty_set/dados.txt b/tests/fixtures/empty_set/dados.txt new file mode 100644 index 0000000..573541a --- /dev/null +++ b/tests/fixtures/empty_set/dados.txt @@ -0,0 +1 @@ +0 diff --git a/tests/fixtures/equivalence_multi_digit/dados.txt b/tests/fixtures/equivalence_multi_digit/dados.txt new file mode 100644 index 0000000..9d643a6 --- /dev/null +++ b/tests/fixtures/equivalence_multi_digit/dados.txt @@ -0,0 +1,5 @@ +2 10 -20 +10 10 +10 -20 +-20 10 +-20 -20 diff --git a/tests/fixtures/invalid_count/dados.txt b/tests/fixtures/invalid_count/dados.txt new file mode 100644 index 0000000..82cced2 --- /dev/null +++ b/tests/fixtures/invalid_count/dados.txt @@ -0,0 +1 @@ +51 diff --git a/tests/fixtures/legacy_sample/dados.txt b/tests/fixtures/legacy_sample/dados.txt new file mode 100644 index 0000000..a89b405 --- /dev/null +++ b/tests/fixtures/legacy_sample/dados.txt @@ -0,0 +1,19 @@ +6 3 4 5 6 7 8 +3 5 +5 7 +7 3 +5 3 +7 5 +3 7 +4 6 +6 8 +8 4 +6 4 +8 6 +4 8 +3 3 +4 4 +5 5 +6 6 +7 7 +8 8 diff --git a/tests/fixtures/malformed_header/dados.txt b/tests/fixtures/malformed_header/dados.txt new file mode 100644 index 0000000..cba3471 --- /dev/null +++ b/tests/fixtures/malformed_header/dados.txt @@ -0,0 +1 @@ +2 1 diff --git a/tests/fixtures/malformed_pair/dados.txt b/tests/fixtures/malformed_pair/dados.txt new file mode 100644 index 0000000..70a5f5b --- /dev/null +++ b/tests/fixtures/malformed_pair/dados.txt @@ -0,0 +1,2 @@ +2 1 2 +1 2 3 diff --git a/tests/fixtures/missing_file/README.md b/tests/fixtures/missing_file/README.md new file mode 100644 index 0000000..fbbcd43 --- /dev/null +++ b/tests/fixtures/missing_file/README.md @@ -0,0 +1 @@ +This directory intentionally contains no `dados.txt` file. diff --git a/tests/fixtures/non_transitive_closure/dados.txt b/tests/fixtures/non_transitive_closure/dados.txt new file mode 100644 index 0000000..929775b --- /dev/null +++ b/tests/fixtures/non_transitive_closure/dados.txt @@ -0,0 +1,3 @@ +3 1 2 3 +1 2 +2 3 diff --git a/tests/fixtures/partial_order/dados.txt b/tests/fixtures/partial_order/dados.txt new file mode 100644 index 0000000..fd2fea7 --- /dev/null +++ b/tests/fixtures/partial_order/dados.txt @@ -0,0 +1,7 @@ +3 1 2 3 +1 1 +2 2 +3 3 +1 2 +2 3 +1 3 diff --git a/tests/fixtures/symmetric_non_transitive/dados.txt b/tests/fixtures/symmetric_non_transitive/dados.txt new file mode 100644 index 0000000..d8aa977 --- /dev/null +++ b/tests/fixtures/symmetric_non_transitive/dados.txt @@ -0,0 +1,5 @@ +3 1 2 3 +1 2 +2 1 +2 3 +3 2 diff --git a/tests/fixtures/unknown_element/dados.txt b/tests/fixtures/unknown_element/dados.txt new file mode 100644 index 0000000..8b8f2af --- /dev/null +++ b/tests/fixtures/unknown_element/dados.txt @@ -0,0 +1,2 @@ +2 1 2 +1 3 diff --git a/tests/run_tests.sh b/tests/run_tests.sh new file mode 100755 index 0000000..ffaff4d --- /dev/null +++ b/tests/run_tests.sh @@ -0,0 +1,165 @@ +#!/usr/bin/env bash + +set -u + +script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +fixtures_dir="${script_dir}/fixtures" +binary_argument="${1:-./a.out}" + +if [[ "${binary_argument}" = /* ]]; then + binary="${binary_argument}" +else + binary="$(cd -- "$(dirname -- "${binary_argument}")" && pwd)/$(basename -- "${binary_argument}")" +fi + +passed=0 +failed=0 + +run_success() { + local fixture="$1" + shift + local output + local expected + local case_failed=0 + + if ! output="$(cd -- "${fixtures_dir}/${fixture}" && "${binary}" 2>&1)"; then + printf 'FAIL %s: expected a successful exit\n%s\n' "${fixture}" "${output}" + failed=$((failed + 1)) + return + fi + + if [[ "${output}" == *'ERROR: AddressSanitizer'* || + "${output}" == *'runtime error:'* || + "${output}" == *'LeakSanitizer'* ]]; then + printf 'FAIL %s: sanitizer diagnostic detected\n%s\n' "${fixture}" "${output}" + failed=$((failed + 1)) + return + fi + + for expected in "$@"; do + if [[ "${output}" != *"${expected}"* ]]; then + printf 'FAIL %s: missing output: %s\n' "${fixture}" "${expected}" + case_failed=1 + fi + done + + if ((case_failed)); then + printf '%s\n' "${output}" + failed=$((failed + 1)) + else + printf 'PASS %s\n' "${fixture}" + passed=$((passed + 1)) + fi +} + +run_failure() { + local fixture="$1" + local expected="$2" + local output + + if output="$(cd -- "${fixtures_dir}/${fixture}" && "${binary}" 2>&1)"; then + printf 'FAIL %s: expected a non-zero exit\n%s\n' "${fixture}" "${output}" + failed=$((failed + 1)) + return + fi + + if [[ "${output}" == *'ERROR: AddressSanitizer'* || + "${output}" == *'runtime error:'* || + "${output}" == *'LeakSanitizer'* ]]; then + printf 'FAIL %s: sanitizer diagnostic detected\n%s\n' "${fixture}" "${output}" + failed=$((failed + 1)) + return + fi + + if [[ "${output}" != *"${expected}"* ]]; then + printf 'FAIL %s: missing error: %s\n%s\n' "${fixture}" "${expected}" "${output}" + failed=$((failed + 1)) + return + fi + + printf 'PASS %s\n' "${fixture}" + passed=$((passed + 1)) +} + +run_success legacy_sample \ + '1. Reflexiva: V' \ + '3. Simetrica: V' \ + '4. Anti-simetrica: F' \ + '6. Transitiva: V' \ + 'Relaçao de equivalencia: V' \ + 'Relaçao de ordem parcial: F' + +run_success empty_set \ + '1. Reflexiva: V' \ + '2. Irreflexiva: V' \ + '3. Simetrica: V' \ + '4. Anti-simetrica: V' \ + '5. Asimetrica: V' \ + '6. Transitiva: V' \ + 'Relaçao de equivalencia: V' \ + 'Relaçao de ordem parcial: V' \ + 'Fecho reflexivo da relaçao = {}' \ + 'Fecho simetrico da relaçao = {}' \ + 'Fecho transitivo da relaçao = {}' + +run_success empty_relation \ + '1. Reflexiva: F' \ + '2. Irreflexiva: V' \ + '3. Simetrica: V' \ + '4. Anti-simetrica: V' \ + '5. Asimetrica: V' \ + '6. Transitiva: V' \ + 'Relaçao de equivalencia: F' \ + 'Relaçao de ordem parcial: F' \ + 'Fecho reflexivo da relaçao = {(1,1),(2,2)}' + +run_success equivalence_multi_digit \ + '1. Reflexiva: V' \ + '2. Irreflexiva: F' \ + '3. Simetrica: V' \ + '4. Anti-simetrica: F' \ + '5. Asimetrica: F' \ + '6. Transitiva: V' \ + 'Relaçao de equivalencia: V' \ + 'Relaçao de ordem parcial: F' + +run_success partial_order \ + '1. Reflexiva: V' \ + '3. Simetrica: F' \ + '4. Anti-simetrica: V' \ + '6. Transitiva: V' \ + 'Relaçao de equivalencia: F' \ + 'Relaçao de ordem parcial: V' + +run_success asymmetric_transitive \ + '1. Reflexiva: F' \ + '2. Irreflexiva: V' \ + '3. Simetrica: F' \ + '4. Anti-simetrica: V' \ + '5. Asimetrica: V' \ + '6. Transitiva: V' + +run_success symmetric_non_transitive \ + '2. Irreflexiva: V' \ + '3. Simetrica: V' \ + '4. Anti-simetrica: F' \ + '5. Asimetrica: F' \ + '6. Transitiva: F' + +run_success non_transitive_closure \ + '6. Transitiva: F' \ + '(1,3);' \ + 'Fecho transitivo da relaçao = {(1,2),(2,3),(1,3)}' + +run_success duplicate_pair \ + 'Fecho simetrico da relaçao = {(10,20),(20,10)}' + +run_failure malformed_header 'quantidade de elementos incorreta' +run_failure duplicate_element 'elementos duplicados' +run_failure unknown_element 'elemento desconhecido' +run_failure malformed_pair 'par ordenado invalido' +run_failure invalid_count 'numero de elementos invalido' +run_failure missing_file 'nao foi possivel abrir o arquivo dados.txt' + +printf '\n%d passed, %d failed\n' "${passed}" "${failed}" +((failed == 0)) From 6a703654a75a0d56ee4ab43a921045fe97c1aed2 Mon Sep 17 00:00:00 2001 From: Lucas Mariz Date: Sat, 12 Sep 2026 11:21:27 -0300 Subject: [PATCH 2/6] refactor: introduce relation domain model --- .gitignore | 2 + Makefile | 30 +- grafo.c | 607 ------------------ grafo.h | 27 - include/application.h | 6 + include/relation.h | 21 + .../legacy_sample/dados.txt => input.txt | 0 main.c | 8 - src/application.c | 470 ++++++++++++++ src/main.c | 5 + src/relation.c | 166 +++++ .../{dados.txt => input.txt} | 0 .../{dados.txt => input.txt} | 0 .../duplicate_pair/{dados.txt => input.txt} | 0 .../empty_relation/{dados.txt => input.txt} | 0 .../empty_set/{dados.txt => input.txt} | 0 .../{dados.txt => input.txt} | 0 .../invalid_count/{dados.txt => input.txt} | 0 .../fixtures/legacy_sample/input.txt | 2 +- .../malformed_header/{dados.txt => input.txt} | 0 .../malformed_pair/{dados.txt => input.txt} | 0 tests/fixtures/missing_file/README.md | 2 +- .../{dados.txt => input.txt} | 0 .../partial_order/{dados.txt => input.txt} | 0 .../{dados.txt => input.txt} | 0 .../unknown_element/{dados.txt => input.txt} | 0 tests/run_tests.sh | 2 +- tests/test_relation.c | 100 +++ 28 files changed, 794 insertions(+), 654 deletions(-) delete mode 100644 grafo.c delete mode 100644 grafo.h create mode 100644 include/application.h create mode 100644 include/relation.h rename tests/fixtures/legacy_sample/dados.txt => input.txt (100%) delete mode 100644 main.c create mode 100644 src/application.c create mode 100644 src/main.c create mode 100644 src/relation.c rename tests/fixtures/asymmetric_transitive/{dados.txt => input.txt} (100%) rename tests/fixtures/duplicate_element/{dados.txt => input.txt} (100%) rename tests/fixtures/duplicate_pair/{dados.txt => input.txt} (100%) rename tests/fixtures/empty_relation/{dados.txt => input.txt} (100%) rename tests/fixtures/empty_set/{dados.txt => input.txt} (100%) rename tests/fixtures/equivalence_multi_digit/{dados.txt => input.txt} (100%) rename tests/fixtures/invalid_count/{dados.txt => input.txt} (100%) rename dados.txt => tests/fixtures/legacy_sample/input.txt (95%) rename tests/fixtures/malformed_header/{dados.txt => input.txt} (100%) rename tests/fixtures/malformed_pair/{dados.txt => input.txt} (100%) rename tests/fixtures/non_transitive_closure/{dados.txt => input.txt} (100%) rename tests/fixtures/partial_order/{dados.txt => input.txt} (100%) rename tests/fixtures/symmetric_non_transitive/{dados.txt => input.txt} (100%) rename tests/fixtures/unknown_element/{dados.txt => input.txt} (100%) create mode 100644 tests/test_relation.c diff --git a/.gitignore b/.gitignore index 3495b9d..ea673b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ /a.out /a.out-sanitize +/relation-tests +/relation-tests-sanitize *.o *.gcda *.gcno diff --git a/Makefile b/Makefile index 1c463f0..bd5778e 100644 --- a/Makefile +++ b/Makefile @@ -10,24 +10,36 @@ SANITIZER_FLAGS := -g -O1 -fno-omit-frame-pointer -fsanitize=address,undefined TARGET := a.out SANITIZER_TARGET := a.out-sanitize -SOURCES := main.c grafo.c -HEADERS := grafo.h +RELATION_TEST_TARGET := relation-tests +SANITIZER_RELATION_TEST_TARGET := relation-tests-sanitize +APP_SOURCES := src/main.c src/application.c src/relation.c +RELATION_SOURCE := src/relation.c +HEADERS := include/application.h include/relation.h .PHONY: all test sanitize clean all: $(TARGET) -$(TARGET): $(SOURCES) $(HEADERS) - $(CC) $(CPPFLAGS) $(STANDARD_FLAGS) $(WARNING_FLAGS) $(CFLAGS) $(SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ +$(TARGET): $(APP_SOURCES) $(HEADERS) + $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(CFLAGS) $(APP_SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ -$(SANITIZER_TARGET): $(SOURCES) $(HEADERS) - $(CC) $(CPPFLAGS) $(STANDARD_FLAGS) $(WARNING_FLAGS) $(SANITIZER_FLAGS) $(SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ +$(SANITIZER_TARGET): $(APP_SOURCES) $(HEADERS) + $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(SANITIZER_FLAGS) $(APP_SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ -test: $(TARGET) + +$(RELATION_TEST_TARGET): tests/test_relation.c $(RELATION_SOURCE) include/relation.h + $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(CFLAGS) tests/test_relation.c $(RELATION_SOURCE) $(LDFLAGS) $(LDLIBS) -o $@ + +$(SANITIZER_RELATION_TEST_TARGET): tests/test_relation.c $(RELATION_SOURCE) include/relation.h + $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(SANITIZER_FLAGS) tests/test_relation.c $(RELATION_SOURCE) $(LDFLAGS) $(LDLIBS) -o $@ + +test: $(TARGET) $(RELATION_TEST_TARGET) + ./$(RELATION_TEST_TARGET) ./tests/run_tests.sh ./$(TARGET) -sanitize: $(SANITIZER_TARGET) +sanitize: $(SANITIZER_TARGET) $(SANITIZER_RELATION_TEST_TARGET) + ASAN_OPTIONS=detect_leaks=$${ASAN_DETECT_LEAKS:-0} ./$(SANITIZER_RELATION_TEST_TARGET) ASAN_OPTIONS=detect_leaks=$${ASAN_DETECT_LEAKS:-0} ./tests/run_tests.sh ./$(SANITIZER_TARGET) clean: - rm -f $(TARGET) $(SANITIZER_TARGET) + rm -f $(TARGET) $(SANITIZER_TARGET) $(RELATION_TEST_TARGET) $(SANITIZER_RELATION_TEST_TARGET) diff --git a/grafo.c b/grafo.c deleted file mode 100644 index 5b83226..0000000 --- a/grafo.c +++ /dev/null @@ -1,607 +0,0 @@ -/* -Trabalho desenvolvido por Lucas Paulo Martins Mariz - Técnico em informática formado pelo Coltec/UFMG E Aluno de Ciência da Computação UFMG -*/ -#include -#include -#include -#include -#include -#include -#include - -#include "grafo.h" - -#define MAX_VERTICES 50 -#define INPUT_BUFFER_SIZE 4096 - -struct grafo { - int **matriz_adjacencia; - int n_vertices; - int elementos[MAX_VERTICES]; - int n_ligacoes; - - int propriedade_reflexiva; - int propriedade_irreflexiva; - int propriedade_simetrica; - int propriedade_anti_simetrica; - int propriedade_assimetrica; - int propriedade_transitiva; - - int relacao_equivalencia; - int relacao_ordem_parcial; -}; - -static int proximo_inteiro(const char **cursor, int *valor) { - char *fim; - long numero; - - while (isspace((unsigned char)**cursor)) { - (*cursor)++; - } - - if (**cursor == '\0') { - return 0; - } - - errno = 0; - numero = strtol(*cursor, &fim, 10); - if (fim == *cursor || errno == ERANGE || numero < INT_MIN || numero > INT_MAX) { - return 0; - } - - *valor = (int)numero; - *cursor = fim; - return 1; -} - -static int contem_apenas_espacos(const char *cursor) { - while (isspace((unsigned char)*cursor)) { - cursor++; - } - return *cursor == '\0'; -} - -static int linha_excedeu_buffer(const char *linha, FILE *arquivo) { - return strchr(linha, '\n') == NULL && !feof(arquivo); -} - -static int **calcula_fecho_transitivo(const Grafo *gr) { - int i, j, k; - int **fecho = aloca_matriz(gr->n_vertices); - - if (gr->n_vertices > 0 && fecho == NULL) { - return NULL; - } - - for (i = 0; i < gr->n_vertices; i++) { - for (j = 0; j < gr->n_vertices; j++) { - fecho[i][j] = gr->matriz_adjacencia[i][j]; - } - } - - for (k = 0; k < gr->n_vertices; k++) { - for (i = 0; i < gr->n_vertices; i++) { - if (fecho[i][k] == 0) { - continue; - } - for (j = 0; j < gr->n_vertices; j++) { - if (fecho[k][j] == 1) { - fecho[i][j] = 1; - } - } - } - } - - return fecho; -} - -Grafo *cria_grafo(int *excessao_zero) { - if (excessao_zero == NULL) { - return NULL; - } - return preenche_grafo(excessao_zero); -} - -void libera_matriz(int **m, int tam) { - int i; - - if (m == NULL) { - return; - } - - for (i = 0; i < tam; i++) { - free(m[i]); - } - free(m); -} - -Grafo *preenche_grafo(int *excessao_zero) { - FILE *arq; - Grafo *gr; - char info[INPUT_BUFFER_SIZE]; - const char *cursor; - int i, j, n_vertices; - size_t numero_linha = 1; - - if (excessao_zero == NULL) { - return NULL; - } - *excessao_zero = 0; - - arq = fopen("dados.txt", "r"); - if (arq == NULL) { - fprintf(stderr, "Erro, nao foi possivel abrir o arquivo dados.txt\n"); - return NULL; - } - - gr = calloc(1, sizeof(*gr)); - if (gr == NULL) { - fprintf(stderr, "Erro, nao foi possivel alocar o grafo\n"); - fclose(arq); - return NULL; - } - - for (i = 0; i < MAX_VERTICES; i++) { - gr->elementos[i] = -1; - } - - if (fgets(info, sizeof(info), arq) == NULL || linha_excedeu_buffer(info, arq)) { - fprintf(stderr, "Erro de entrada na linha 1: cabecalho ausente ou muito longo\n"); - goto erro; - } - - cursor = info; - if (!proximo_inteiro(&cursor, &n_vertices) || n_vertices < 0 || n_vertices > MAX_VERTICES) { - fprintf(stderr, "Erro de entrada na linha 1: numero de elementos invalido\n"); - goto erro; - } - gr->n_vertices = n_vertices; - - for (i = 0; i < gr->n_vertices; i++) { - if (!proximo_inteiro(&cursor, &gr->elementos[i])) { - fprintf(stderr, "Erro de entrada na linha 1: quantidade de elementos incorreta\n"); - goto erro; - } - for (j = 0; j < i; j++) { - if (gr->elementos[j] == gr->elementos[i]) { - fprintf(stderr, "Erro de entrada na linha 1: elementos duplicados\n"); - goto erro; - } - } - } - - if (!contem_apenas_espacos(cursor)) { - fprintf(stderr, "Erro de entrada na linha 1: quantidade de elementos incorreta\n"); - goto erro; - } - - if (gr->n_vertices > 0) { - gr->matriz_adjacencia = aloca_matriz(gr->n_vertices); - if (gr->matriz_adjacencia == NULL) { - fprintf(stderr, "Erro, nao foi possivel alocar a matriz de adjacencia\n"); - goto erro; - } - } - - while (fgets(info, sizeof(info), arq) != NULL) { - int origem, destino; - - numero_linha++; - if (linha_excedeu_buffer(info, arq)) { - fprintf(stderr, "Erro de entrada na linha %zu: linha muito longa\n", numero_linha); - goto erro; - } - - cursor = info; - while (isspace((unsigned char)*cursor)) { - cursor++; - } - if (*cursor == '\0') { - continue; - } - - if (!proximo_inteiro(&cursor, &origem) || !proximo_inteiro(&cursor, &destino) || - !contem_apenas_espacos(cursor)) { - fprintf(stderr, "Erro de entrada na linha %zu: par ordenado invalido\n", numero_linha); - goto erro; - } - - if (!insere_aresta(gr, origem, destino)) { - fprintf(stderr, "Erro de entrada na linha %zu: elemento desconhecido\n", numero_linha); - goto erro; - } - } - - if (ferror(arq)) { - fprintf(stderr, "Erro durante a leitura de dados.txt\n"); - goto erro; - } - - gr->propriedade_reflexiva = 1; - gr->propriedade_irreflexiva = 1; - gr->propriedade_simetrica = 1; - gr->propriedade_anti_simetrica = 1; - gr->propriedade_assimetrica = 1; - gr->propriedade_transitiva = 1; - gr->relacao_equivalencia = 1; - gr->relacao_ordem_parcial = 1; - *excessao_zero = gr->n_vertices == 0; - - fclose(arq); - return gr; - -erro: - fclose(arq); - libera_matriz(gr->matriz_adjacencia, gr->n_vertices); - free(gr); - return NULL; -} - -int insere_aresta(Grafo *gr, int orig, int dest) { - int i; - int nova_origem = -1; - int novo_destino = -1; - - if (gr == NULL) { - return 0; - } - - for (i = 0; i < gr->n_vertices; i++) { - if (gr->elementos[i] == orig) { - nova_origem = i; - } - if (gr->elementos[i] == dest) { - novo_destino = i; - } - } - - if (nova_origem < 0 || novo_destino < 0) { - return 0; - } - - if (gr->matriz_adjacencia[nova_origem][novo_destino] == 0) { - gr->matriz_adjacencia[nova_origem][novo_destino] = 1; - gr->n_ligacoes++; - } - return 1; -} - -void imprime_matriz(Grafo *gr) { - int i, j; - - printf("\n\t"); - for (i = 0; i < gr->n_vertices; i++) { - printf("%d ", gr->elementos[i]); - } - - printf("\n\n\n"); - for (i = 0; i < gr->n_vertices; i++) { - printf("%d\t", gr->elementos[i]); - for (j = 0; j < gr->n_vertices; j++) { - printf("%d ", gr->matriz_adjacencia[i][j]); - } - printf("\n"); - } - printf("\n\n"); -} - -int **aloca_matriz(int tam) { - int i; - int **m; - - if (tam <= 0) { - return NULL; - } - - m = calloc((size_t)tam, sizeof(*m)); - if (m == NULL) { - return NULL; - } - - for (i = 0; i < tam; i++) { - m[i] = calloc((size_t)tam, sizeof(*m[i])); - if (m[i] == NULL) { - libera_matriz(m, i); - return NULL; - } - } - return m; -} - -void reflexiva(Grafo *gr) { - int i; - - gr->propriedade_reflexiva = 1; - for (i = 0; i < gr->n_vertices; i++) { - if (gr->matriz_adjacencia[i][i] == 0) { - gr->propriedade_reflexiva = 0; - } - } - - if (gr->propriedade_reflexiva == 1) { - printf("1. Reflexiva: V\n"); - return; - } - - printf("1. Reflexiva: F\n"); - for (i = 0; i < gr->n_vertices; i++) { - if (gr->matriz_adjacencia[i][i] == 0) { - printf("(%d,%d); ", gr->elementos[i], gr->elementos[i]); - } - } - printf("\n"); -} - -void irreflexiva(Grafo *gr) { - int i; - - gr->propriedade_irreflexiva = 1; - for (i = 0; i < gr->n_vertices; i++) { - if (gr->matriz_adjacencia[i][i] == 1) { - gr->propriedade_irreflexiva = 0; - } - } - - if (gr->propriedade_irreflexiva == 1) { - printf("2. Irreflexiva: V\n"); - return; - } - - printf("2. Irreflexiva: F\n"); - for (i = 0; i < gr->n_vertices; i++) { - if (gr->matriz_adjacencia[i][i] == 1) { - printf("(%d,%d); ", gr->elementos[i], gr->elementos[i]); - } - } - printf("\n"); -} - -void simetrica(Grafo *gr) { - int i, j; - - gr->propriedade_simetrica = 1; - for (i = 0; i < gr->n_vertices; i++) { - for (j = 0; j < gr->n_vertices; j++) { - if (gr->matriz_adjacencia[i][j] == 1 && gr->matriz_adjacencia[j][i] == 0) { - gr->propriedade_simetrica = 0; - } - } - } - - if (gr->propriedade_simetrica == 1) { - printf("3. Simetrica: V\n"); - return; - } - - printf("3. Simetrica: F\n"); - for (i = 0; i < gr->n_vertices; i++) { - for (j = 0; j < gr->n_vertices; j++) { - if (gr->matriz_adjacencia[i][j] == 1 && gr->matriz_adjacencia[j][i] == 0) { - printf("(%d,%d) e (%d,%d); ", gr->elementos[i], gr->elementos[j], - gr->elementos[j], gr->elementos[i]); - } - } - } - printf("\n"); -} - -void anti_simetrica(Grafo *gr) { - int i, j; - - gr->propriedade_anti_simetrica = 1; - for (i = 0; i < gr->n_vertices; i++) { - for (j = i + 1; j < gr->n_vertices; j++) { - if (gr->matriz_adjacencia[i][j] == 1 && gr->matriz_adjacencia[j][i] == 1) { - gr->propriedade_anti_simetrica = 0; - } - } - } - - if (gr->propriedade_anti_simetrica == 1) { - printf("4. Anti-simetrica: V\n"); - return; - } - - printf("4. Anti-simetrica: F\n"); - for (i = 0; i < gr->n_vertices; i++) { - for (j = i + 1; j < gr->n_vertices; j++) { - if (gr->matriz_adjacencia[i][j] == 1 && gr->matriz_adjacencia[j][i] == 1) { - printf("(%d,%d) e (%d,%d); ", gr->elementos[i], gr->elementos[j], - gr->elementos[j], gr->elementos[i]); - } - } - } - printf("\n"); -} - -void assimetrica(Grafo *gr) { - int i, j; - - gr->propriedade_assimetrica = 1; - for (i = 0; i < gr->n_vertices; i++) { - for (j = 0; j < gr->n_vertices; j++) { - if (gr->matriz_adjacencia[i][j] == 1 && gr->matriz_adjacencia[j][i] == 1) { - gr->propriedade_assimetrica = 0; - } - } - } - - if (gr->propriedade_assimetrica == 1) { - printf("5. Asimetrica: V\n"); - } else { - printf("5. Asimetrica: F\n"); - } -} - -int transitiva(Grafo *gr) { - int i, j; - int **fecho = calcula_fecho_transitivo(gr); - - if (gr->n_vertices > 0 && fecho == NULL) { - fprintf(stderr, "Erro, nao foi possivel calcular o fecho transitivo\n"); - return -1; - } - - gr->propriedade_transitiva = 1; - for (i = 0; i < gr->n_vertices; i++) { - for (j = 0; j < gr->n_vertices; j++) { - if (fecho[i][j] == 1 && gr->matriz_adjacencia[i][j] == 0) { - gr->propriedade_transitiva = 0; - } - } - } - - if (gr->propriedade_transitiva == 1) { - printf("6. Transitiva: V\n"); - } else { - printf("6. Transitiva: F\n"); - for (i = 0; i < gr->n_vertices; i++) { - for (j = 0; j < gr->n_vertices; j++) { - if (fecho[i][j] == 1 && gr->matriz_adjacencia[i][j] == 0) { - printf("(%d,%d); ", gr->elementos[i], gr->elementos[j]); - } - } - } - printf("\n"); - } - - libera_matriz(fecho, gr->n_vertices); - return 0; -} - -void equivalencia(Grafo *gr) { - gr->relacao_equivalencia = gr->propriedade_reflexiva == 1 && - gr->propriedade_simetrica == 1 && - gr->propriedade_transitiva == 1; - printf("Relaçao de equivalencia: %c\n", gr->relacao_equivalencia ? 'V' : 'F'); -} - -void ordem_parcial(Grafo *gr) { - gr->relacao_ordem_parcial = gr->propriedade_reflexiva == 1 && - gr->propriedade_anti_simetrica == 1 && - gr->propriedade_transitiva == 1; - printf("Relaçao de ordem parcial: %c\n", gr->relacao_ordem_parcial ? 'V' : 'F'); -} - -void fecho_padrao(Grafo *gr) { - int i, j; - bool primeiro = true; - - for (i = 0; i < gr->n_vertices; i++) { - for (j = 0; j < gr->n_vertices; j++) { - if (gr->matriz_adjacencia[i][j] == 1) { - if (!primeiro) { - printf(","); - } - printf("(%d,%d)", gr->elementos[i], gr->elementos[j]); - primeiro = false; - } - } - } -} - -void fecho_reflexivo(Grafo *gr) { - int i; - bool precisa_virgula = gr->n_ligacoes > 0; - - printf("Fecho reflexivo da relaçao = {"); - fecho_padrao(gr); - for (i = 0; i < gr->n_vertices; i++) { - if (gr->matriz_adjacencia[i][i] == 0) { - if (precisa_virgula) { - printf(","); - } - printf("(%d,%d)", gr->elementos[i], gr->elementos[i]); - precisa_virgula = true; - } - } - printf("}\n"); -} - -void fecho_simetrico(Grafo *gr) { - int i, j; - bool precisa_virgula = gr->n_ligacoes > 0; - - printf("Fecho simetrico da relaçao = {"); - fecho_padrao(gr); - for (i = 0; i < gr->n_vertices; i++) { - for (j = 0; j < gr->n_vertices; j++) { - if (gr->matriz_adjacencia[i][j] == 1 && gr->matriz_adjacencia[j][i] == 0) { - if (precisa_virgula) { - printf(","); - } - printf("(%d,%d)", gr->elementos[j], gr->elementos[i]); - precisa_virgula = true; - } - } - } - printf("}\n"); -} - -int fecho_transitivo(Grafo *gr) { - int i, j; - bool precisa_virgula = gr->n_ligacoes > 0; - int **fecho = calcula_fecho_transitivo(gr); - - if (gr->n_vertices > 0 && fecho == NULL) { - fprintf(stderr, "Erro, nao foi possivel calcular o fecho transitivo\n"); - return -1; - } - - printf("Fecho transitivo da relaçao = {"); - fecho_padrao(gr); - for (i = 0; i < gr->n_vertices; i++) { - for (j = 0; j < gr->n_vertices; j++) { - if (fecho[i][j] == 1 && gr->matriz_adjacencia[i][j] == 0) { - if (precisa_virgula) { - printf(","); - } - printf("(%d,%d)", gr->elementos[i], gr->elementos[j]); - precisa_virgula = true; - } - } - } - printf("}\n"); - - libera_matriz(fecho, gr->n_vertices); - return 0; -} - -int propriedades(Grafo *gr) { - printf("\n\nPropriedades:\n\n"); - imprime_matriz(gr); - reflexiva(gr); - irreflexiva(gr); - simetrica(gr); - anti_simetrica(gr); - assimetrica(gr); - if (transitiva(gr) != 0) { - return -1; - } - printf("\n"); - equivalencia(gr); - ordem_parcial(gr); - printf("\n"); - fecho_reflexivo(gr); - fecho_simetrico(gr); - if (fecho_transitivo(gr) != 0) { - return -1; - } - printf("\n"); - return 0; -} - -int inicializar(void) { - int excessao_zero; - Grafo *gr = cria_grafo(&excessao_zero); - int resultado; - - if (gr == NULL) { - return EXIT_FAILURE; - } - - resultado = propriedades(gr); - libera_matriz(gr->matriz_adjacencia, gr->n_vertices); - free(gr); - return resultado == 0 ? EXIT_SUCCESS : EXIT_FAILURE; -} diff --git a/grafo.h b/grafo.h deleted file mode 100644 index f9d9c27..0000000 --- a/grafo.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef GRAFO_H -#define GRAFO_H - -typedef struct grafo Grafo; - -Grafo *cria_grafo(int *excessao_zero); -Grafo *preenche_grafo(int *excessao_zero); -int insere_aresta(Grafo *gr, int orig, int dest); -void imprime_matriz(Grafo *gr); -int inicializar(void); -int propriedades(Grafo *gr); -void libera_matriz(int **m, int tam); -int **aloca_matriz(int tam); -void reflexiva(Grafo *gr); -void irreflexiva(Grafo *gr); -void simetrica(Grafo *gr); -void anti_simetrica(Grafo *gr); -void assimetrica(Grafo *gr); -int transitiva(Grafo *gr); -void equivalencia(Grafo *gr); -void ordem_parcial(Grafo *gr); -void fecho_reflexivo(Grafo *gr); -void fecho_simetrico(Grafo *gr); -int fecho_transitivo(Grafo *gr); -void fecho_padrao(Grafo *gr); - -#endif diff --git a/include/application.h b/include/application.h new file mode 100644 index 0000000..0ccf664 --- /dev/null +++ b/include/application.h @@ -0,0 +1,6 @@ +#ifndef BINARY_RELATION_ANALYZER_APPLICATION_H +#define BINARY_RELATION_ANALYZER_APPLICATION_H + +int run_application(void); + +#endif diff --git a/include/relation.h b/include/relation.h new file mode 100644 index 0000000..a5ba115 --- /dev/null +++ b/include/relation.h @@ -0,0 +1,21 @@ +#ifndef BINARY_RELATION_ANALYZER_RELATION_H +#define BINARY_RELATION_ANALYZER_RELATION_H + +#include +#include + +typedef struct Relation Relation; + +Relation *relation_create(const int *elements, size_t element_count); +Relation *relation_clone(const Relation *relation); +void relation_destroy(Relation *relation); + +size_t relation_size(const Relation *relation); +size_t relation_pair_count(const Relation *relation); +bool relation_element_at(const Relation *relation, size_t index, int *element); +bool relation_index_of(const Relation *relation, int element, size_t *index); +bool relation_contains(const Relation *relation, size_t source_index, size_t target_index); +bool relation_add_pair(Relation *relation, int source, int target); +bool relation_add_pair_at(Relation *relation, size_t source_index, size_t target_index); + +#endif diff --git a/tests/fixtures/legacy_sample/dados.txt b/input.txt similarity index 100% rename from tests/fixtures/legacy_sample/dados.txt rename to input.txt diff --git a/main.c b/main.c deleted file mode 100644 index ea68ab5..0000000 --- a/main.c +++ /dev/null @@ -1,8 +0,0 @@ -/* -Trabalho desenvolvido por Lucas Paulo Martins Mariz - Técnico em informática formado pelo Coltec/UFMG E Aluno de Ciência da Computação UFMG -*/ -#include "grafo.h" - -int main(void) { - return inicializar(); -} diff --git a/src/application.c b/src/application.c new file mode 100644 index 0000000..6f0269d --- /dev/null +++ b/src/application.c @@ -0,0 +1,470 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "application.h" +#include "relation.h" + +#define INPUT_FILE "input.txt" +#define INPUT_BUFFER_SIZE 4096 +#define MAX_INPUT_ELEMENTS 50 + +typedef struct { + bool reflexive; + bool irreflexive; + bool symmetric; + bool antisymmetric; + bool asymmetric; + bool transitive; + bool equivalence; + bool partial_order; +} RelationAnalysis; + +static bool parse_next_integer(const char **cursor, int *value) { + char *end; + long number; + + while (isspace((unsigned char)**cursor)) { + (*cursor)++; + } + if (**cursor == '\0') { + return false; + } + + errno = 0; + number = strtol(*cursor, &end, 10); + if (end == *cursor || errno == ERANGE || number < INT_MIN || number > INT_MAX) { + return false; + } + + *value = (int)number; + *cursor = end; + return true; +} + +static bool contains_only_whitespace(const char *cursor) { + while (isspace((unsigned char)*cursor)) { + cursor++; + } + return *cursor == '\0'; +} + +static bool line_exceeds_buffer(const char *line, FILE *input) { + return strchr(line, '\n') == NULL && !feof(input); +} + +static bool contains_element(const int *elements, size_t element_count, int element) { + size_t index; + + for (index = 0; index < element_count; index++) { + if (elements[index] == element) { + return true; + } + } + return false; +} + +static Relation *load_relation(const char *filename) { + FILE *input = fopen(filename, "r"); + Relation *relation = NULL; + char line[INPUT_BUFFER_SIZE]; + const char *cursor; + int declared_count; + int elements[MAX_INPUT_ELEMENTS]; + size_t line_number = 1; + size_t index; + + if (input == NULL) { + fprintf(stderr, "Erro, nao foi possivel abrir o arquivo %s\n", filename); + return NULL; + } + + if (fgets(line, sizeof(line), input) == NULL || line_exceeds_buffer(line, input)) { + fprintf(stderr, "Erro de entrada na linha 1: cabecalho ausente ou muito longo\n"); + goto error; + } + + cursor = line; + if (!parse_next_integer(&cursor, &declared_count) || declared_count < 0 || + declared_count > MAX_INPUT_ELEMENTS) { + fprintf(stderr, "Erro de entrada na linha 1: numero de elementos invalido\n"); + goto error; + } + + for (index = 0; index < (size_t)declared_count; index++) { + if (!parse_next_integer(&cursor, &elements[index])) { + fprintf(stderr, "Erro de entrada na linha 1: quantidade de elementos incorreta\n"); + goto error; + } + if (contains_element(elements, index, elements[index])) { + fprintf(stderr, "Erro de entrada na linha 1: elementos duplicados\n"); + goto error; + } + } + + if (!contains_only_whitespace(cursor)) { + fprintf(stderr, "Erro de entrada na linha 1: quantidade de elementos incorreta\n"); + goto error; + } + + relation = relation_create(elements, (size_t)declared_count); + if (relation == NULL) { + fprintf(stderr, "Erro, nao foi possivel alocar a relacao\n"); + goto error; + } + + while (fgets(line, sizeof(line), input) != NULL) { + int source; + int target; + + line_number++; + if (line_exceeds_buffer(line, input)) { + fprintf(stderr, "Erro de entrada na linha %zu: linha muito longa\n", line_number); + goto error; + } + + cursor = line; + while (isspace((unsigned char)*cursor)) { + cursor++; + } + if (*cursor == '\0') { + continue; + } + + if (!parse_next_integer(&cursor, &source) || !parse_next_integer(&cursor, &target) || + !contains_only_whitespace(cursor)) { + fprintf(stderr, "Erro de entrada na linha %zu: par ordenado invalido\n", line_number); + goto error; + } + + if (!relation_add_pair(relation, source, target)) { + fprintf(stderr, "Erro de entrada na linha %zu: elemento desconhecido\n", line_number); + goto error; + } + } + + if (ferror(input)) { + fprintf(stderr, "Erro durante a leitura de %s\n", filename); + goto error; + } + + fclose(input); + return relation; + +error: + fclose(input); + relation_destroy(relation); + return NULL; +} + +static int element_at(const Relation *relation, size_t index) { + int element = 0; + + (void)relation_element_at(relation, index, &element); + return element; +} + +static Relation *create_transitive_closure(const Relation *relation) { + Relation *closure = relation_clone(relation); + size_t size = relation_size(relation); + size_t intermediate; + size_t source; + size_t target; + + if (closure == NULL) { + return NULL; + } + + for (intermediate = 0; intermediate < size; intermediate++) { + for (source = 0; source < size; source++) { + if (!relation_contains(closure, source, intermediate)) { + continue; + } + for (target = 0; target < size; target++) { + if (relation_contains(closure, intermediate, target)) { + (void)relation_add_pair_at(closure, source, target); + } + } + } + } + return closure; +} + +static RelationAnalysis analyze_relation(const Relation *relation, + const Relation *transitive_closure) { + RelationAnalysis analysis = {true, true, true, true, true, true, false, false}; + size_t size = relation_size(relation); + size_t source; + size_t target; + + for (source = 0; source < size; source++) { + if (relation_contains(relation, source, source)) { + analysis.irreflexive = false; + analysis.asymmetric = false; + } else { + analysis.reflexive = false; + } + + for (target = source + 1; target < size; target++) { + bool forward = relation_contains(relation, source, target); + bool reverse = relation_contains(relation, target, source); + + if (forward != reverse) { + analysis.symmetric = false; + } + if (forward && reverse) { + analysis.antisymmetric = false; + analysis.asymmetric = false; + } + } + } + + for (source = 0; source < size; source++) { + for (target = 0; target < size; target++) { + if (relation_contains(transitive_closure, source, target) && + !relation_contains(relation, source, target)) { + analysis.transitive = false; + } + } + } + + analysis.equivalence = analysis.reflexive && analysis.symmetric && analysis.transitive; + analysis.partial_order = analysis.reflexive && analysis.antisymmetric && analysis.transitive; + return analysis; +} + +static void print_matrix(const Relation *relation) { + size_t size = relation_size(relation); + size_t row; + size_t column; + + printf("\n\t"); + for (column = 0; column < size; column++) { + printf("%d ", element_at(relation, column)); + } + + printf("\n\n\n"); + for (row = 0; row < size; row++) { + printf("%d\t", element_at(relation, row)); + for (column = 0; column < size; column++) { + printf("%d ", relation_contains(relation, row, column) ? 1 : 0); + } + printf("\n"); + } + printf("\n\n"); +} + +static void print_reflexivity(const Relation *relation, bool reflexive) { + size_t index; + + printf("1. Reflexiva: %c\n", reflexive ? 'V' : 'F'); + if (reflexive) { + return; + } + + for (index = 0; index < relation_size(relation); index++) { + if (!relation_contains(relation, index, index)) { + printf("(%d,%d); ", element_at(relation, index), element_at(relation, index)); + } + } + printf("\n"); +} + +static void print_irreflexivity(const Relation *relation, bool irreflexive) { + size_t index; + + printf("2. Irreflexiva: %c\n", irreflexive ? 'V' : 'F'); + if (irreflexive) { + return; + } + + for (index = 0; index < relation_size(relation); index++) { + if (relation_contains(relation, index, index)) { + printf("(%d,%d); ", element_at(relation, index), element_at(relation, index)); + } + } + printf("\n"); +} + +static void print_symmetry(const Relation *relation, bool symmetric) { + size_t source; + size_t target; + + printf("3. Simetrica: %c\n", symmetric ? 'V' : 'F'); + if (symmetric) { + return; + } + + for (source = 0; source < relation_size(relation); source++) { + for (target = 0; target < relation_size(relation); target++) { + if (relation_contains(relation, source, target) && + !relation_contains(relation, target, source)) { + printf("(%d,%d) e (%d,%d); ", element_at(relation, source), + element_at(relation, target), element_at(relation, target), + element_at(relation, source)); + } + } + } + printf("\n"); +} + +static void print_antisymmetry(const Relation *relation, bool antisymmetric) { + size_t source; + size_t target; + + printf("4. Anti-simetrica: %c\n", antisymmetric ? 'V' : 'F'); + if (antisymmetric) { + return; + } + + for (source = 0; source < relation_size(relation); source++) { + for (target = source + 1; target < relation_size(relation); target++) { + if (relation_contains(relation, source, target) && + relation_contains(relation, target, source)) { + printf("(%d,%d) e (%d,%d); ", element_at(relation, source), + element_at(relation, target), element_at(relation, target), + element_at(relation, source)); + } + } + } + printf("\n"); +} + +static void print_transitivity(const Relation *relation, const Relation *closure, + bool transitive) { + size_t source; + size_t target; + + printf("6. Transitiva: %c\n", transitive ? 'V' : 'F'); + if (transitive) { + return; + } + + for (source = 0; source < relation_size(relation); source++) { + for (target = 0; target < relation_size(relation); target++) { + if (relation_contains(closure, source, target) && + !relation_contains(relation, source, target)) { + printf("(%d,%d); ", element_at(relation, source), + element_at(relation, target)); + } + } + } + printf("\n"); +} + +static void print_pair(int source, int target, bool *first) { + if (!*first) { + printf(","); + } + printf("(%d,%d)", source, target); + *first = false; +} + +static void print_existing_pairs(const Relation *relation, bool *first) { + size_t source; + size_t target; + + for (source = 0; source < relation_size(relation); source++) { + for (target = 0; target < relation_size(relation); target++) { + if (relation_contains(relation, source, target)) { + print_pair(element_at(relation, source), element_at(relation, target), first); + } + } + } +} + +static void print_reflexive_closure(const Relation *relation) { + bool first = true; + size_t index; + + printf("Fecho reflexivo da relaçao = {"); + print_existing_pairs(relation, &first); + for (index = 0; index < relation_size(relation); index++) { + if (!relation_contains(relation, index, index)) { + print_pair(element_at(relation, index), element_at(relation, index), &first); + } + } + printf("}\n"); +} + +static void print_symmetric_closure(const Relation *relation) { + bool first = true; + size_t source; + size_t target; + + printf("Fecho simetrico da relaçao = {"); + print_existing_pairs(relation, &first); + for (source = 0; source < relation_size(relation); source++) { + for (target = 0; target < relation_size(relation); target++) { + if (relation_contains(relation, source, target) && + !relation_contains(relation, target, source)) { + print_pair(element_at(relation, target), element_at(relation, source), &first); + } + } + } + printf("}\n"); +} + +static void print_transitive_closure(const Relation *relation, const Relation *closure) { + bool first = true; + size_t source; + size_t target; + + printf("Fecho transitivo da relaçao = {"); + print_existing_pairs(relation, &first); + for (source = 0; source < relation_size(relation); source++) { + for (target = 0; target < relation_size(relation); target++) { + if (relation_contains(closure, source, target) && + !relation_contains(relation, source, target)) { + print_pair(element_at(relation, source), element_at(relation, target), &first); + } + } + } + printf("}\n"); +} + +static bool print_report(const Relation *relation) { + Relation *transitive_closure = create_transitive_closure(relation); + RelationAnalysis analysis; + + if (transitive_closure == NULL) { + fprintf(stderr, "Erro, nao foi possivel calcular o fecho transitivo\n"); + return false; + } + + analysis = analyze_relation(relation, transitive_closure); + printf("\n\nPropriedades:\n\n"); + print_matrix(relation); + print_reflexivity(relation, analysis.reflexive); + print_irreflexivity(relation, analysis.irreflexive); + print_symmetry(relation, analysis.symmetric); + print_antisymmetry(relation, analysis.antisymmetric); + printf("5. Asimetrica: %c\n", analysis.asymmetric ? 'V' : 'F'); + print_transitivity(relation, transitive_closure, analysis.transitive); + printf("\nRelaçao de equivalencia: %c\n", analysis.equivalence ? 'V' : 'F'); + printf("Relaçao de ordem parcial: %c\n\n", analysis.partial_order ? 'V' : 'F'); + print_reflexive_closure(relation); + print_symmetric_closure(relation); + print_transitive_closure(relation, transitive_closure); + printf("\n"); + + relation_destroy(transitive_closure); + return true; +} + +int run_application(void) { + Relation *relation = load_relation(INPUT_FILE); + bool success; + + if (relation == NULL) { + return EXIT_FAILURE; + } + + success = print_report(relation); + relation_destroy(relation); + return success ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..e354edc --- /dev/null +++ b/src/main.c @@ -0,0 +1,5 @@ +#include "application.h" + +int main(void) { + return run_application(); +} diff --git a/src/relation.c b/src/relation.c new file mode 100644 index 0000000..e0fd079 --- /dev/null +++ b/src/relation.c @@ -0,0 +1,166 @@ +#include +#include +#include + +#include "relation.h" + +struct Relation { + int *elements; + bool *pairs; + size_t element_count; + size_t pair_count; +}; + +static bool allocation_sizes_are_valid(size_t element_count) { + size_t pair_capacity; + + if (element_count > SIZE_MAX / sizeof(int)) { + return false; + } + if (element_count > 0 && element_count > SIZE_MAX / element_count) { + return false; + } + pair_capacity = element_count * element_count; + return pair_capacity <= SIZE_MAX / sizeof(bool); +} + +static bool elements_are_unique(const int *elements, size_t element_count) { + size_t current; + size_t previous; + + for (current = 0; current < element_count; current++) { + for (previous = 0; previous < current; previous++) { + if (elements[current] == elements[previous]) { + return false; + } + } + } + return true; +} + +static size_t pair_offset(const Relation *relation, size_t source_index, size_t target_index) { + return source_index * relation->element_count + target_index; +} + +Relation *relation_create(const int *elements, size_t element_count) { + Relation *relation; + + if ((element_count > 0 && elements == NULL) || !allocation_sizes_are_valid(element_count) || + !elements_are_unique(elements, element_count)) { + return NULL; + } + + relation = calloc(1, sizeof(*relation)); + if (relation == NULL) { + return NULL; + } + + if (element_count > 0) { + relation->elements = malloc(element_count * sizeof(*relation->elements)); + relation->pairs = calloc(element_count * element_count, sizeof(*relation->pairs)); + if (relation->elements == NULL || relation->pairs == NULL) { + relation_destroy(relation); + return NULL; + } + memcpy(relation->elements, elements, element_count * sizeof(*relation->elements)); + } + + relation->element_count = element_count; + return relation; +} + +Relation *relation_clone(const Relation *relation) { + Relation *clone; + + if (relation == NULL) { + return NULL; + } + + clone = relation_create(relation->elements, relation->element_count); + if (clone == NULL) { + return NULL; + } + + if (relation->element_count > 0) { + memcpy(clone->pairs, relation->pairs, + relation->element_count * relation->element_count * sizeof(*clone->pairs)); + } + clone->pair_count = relation->pair_count; + return clone; +} + +void relation_destroy(Relation *relation) { + if (relation == NULL) { + return; + } + free(relation->pairs); + free(relation->elements); + free(relation); +} + +size_t relation_size(const Relation *relation) { + return relation == NULL ? 0 : relation->element_count; +} + +size_t relation_pair_count(const Relation *relation) { + return relation == NULL ? 0 : relation->pair_count; +} + +bool relation_element_at(const Relation *relation, size_t index, int *element) { + if (relation == NULL || element == NULL || index >= relation->element_count) { + return false; + } + *element = relation->elements[index]; + return true; +} + +bool relation_index_of(const Relation *relation, int element, size_t *index) { + size_t current; + + if (relation == NULL || index == NULL) { + return false; + } + + for (current = 0; current < relation->element_count; current++) { + if (relation->elements[current] == element) { + *index = current; + return true; + } + } + return false; +} + +bool relation_contains(const Relation *relation, size_t source_index, size_t target_index) { + if (relation == NULL || source_index >= relation->element_count || + target_index >= relation->element_count) { + return false; + } + return relation->pairs[pair_offset(relation, source_index, target_index)]; +} + +bool relation_add_pair(Relation *relation, int source, int target) { + size_t source_index; + size_t target_index; + + if (!relation_index_of(relation, source, &source_index) || + !relation_index_of(relation, target, &target_index)) { + return false; + } + return relation_add_pair_at(relation, source_index, target_index); +} + +bool relation_add_pair_at(Relation *relation, size_t source_index, size_t target_index) { + size_t offset; + + if (relation == NULL || source_index >= relation->element_count || + target_index >= relation->element_count) { + return false; + } + + offset = pair_offset(relation, source_index, target_index); + if (!relation->pairs[offset]) { + relation->pairs[offset] = true; + relation->pair_count++; + } + return true; +} diff --git a/tests/fixtures/asymmetric_transitive/dados.txt b/tests/fixtures/asymmetric_transitive/input.txt similarity index 100% rename from tests/fixtures/asymmetric_transitive/dados.txt rename to tests/fixtures/asymmetric_transitive/input.txt diff --git a/tests/fixtures/duplicate_element/dados.txt b/tests/fixtures/duplicate_element/input.txt similarity index 100% rename from tests/fixtures/duplicate_element/dados.txt rename to tests/fixtures/duplicate_element/input.txt diff --git a/tests/fixtures/duplicate_pair/dados.txt b/tests/fixtures/duplicate_pair/input.txt similarity index 100% rename from tests/fixtures/duplicate_pair/dados.txt rename to tests/fixtures/duplicate_pair/input.txt diff --git a/tests/fixtures/empty_relation/dados.txt b/tests/fixtures/empty_relation/input.txt similarity index 100% rename from tests/fixtures/empty_relation/dados.txt rename to tests/fixtures/empty_relation/input.txt diff --git a/tests/fixtures/empty_set/dados.txt b/tests/fixtures/empty_set/input.txt similarity index 100% rename from tests/fixtures/empty_set/dados.txt rename to tests/fixtures/empty_set/input.txt diff --git a/tests/fixtures/equivalence_multi_digit/dados.txt b/tests/fixtures/equivalence_multi_digit/input.txt similarity index 100% rename from tests/fixtures/equivalence_multi_digit/dados.txt rename to tests/fixtures/equivalence_multi_digit/input.txt diff --git a/tests/fixtures/invalid_count/dados.txt b/tests/fixtures/invalid_count/input.txt similarity index 100% rename from tests/fixtures/invalid_count/dados.txt rename to tests/fixtures/invalid_count/input.txt diff --git a/dados.txt b/tests/fixtures/legacy_sample/input.txt similarity index 95% rename from dados.txt rename to tests/fixtures/legacy_sample/input.txt index ab6cf7f..a89b405 100644 --- a/dados.txt +++ b/tests/fixtures/legacy_sample/input.txt @@ -16,4 +16,4 @@ 5 5 6 6 7 7 -8 8 \ No newline at end of file +8 8 diff --git a/tests/fixtures/malformed_header/dados.txt b/tests/fixtures/malformed_header/input.txt similarity index 100% rename from tests/fixtures/malformed_header/dados.txt rename to tests/fixtures/malformed_header/input.txt diff --git a/tests/fixtures/malformed_pair/dados.txt b/tests/fixtures/malformed_pair/input.txt similarity index 100% rename from tests/fixtures/malformed_pair/dados.txt rename to tests/fixtures/malformed_pair/input.txt diff --git a/tests/fixtures/missing_file/README.md b/tests/fixtures/missing_file/README.md index fbbcd43..9816f90 100644 --- a/tests/fixtures/missing_file/README.md +++ b/tests/fixtures/missing_file/README.md @@ -1 +1 @@ -This directory intentionally contains no `dados.txt` file. +This directory intentionally contains no `input.txt` file. diff --git a/tests/fixtures/non_transitive_closure/dados.txt b/tests/fixtures/non_transitive_closure/input.txt similarity index 100% rename from tests/fixtures/non_transitive_closure/dados.txt rename to tests/fixtures/non_transitive_closure/input.txt diff --git a/tests/fixtures/partial_order/dados.txt b/tests/fixtures/partial_order/input.txt similarity index 100% rename from tests/fixtures/partial_order/dados.txt rename to tests/fixtures/partial_order/input.txt diff --git a/tests/fixtures/symmetric_non_transitive/dados.txt b/tests/fixtures/symmetric_non_transitive/input.txt similarity index 100% rename from tests/fixtures/symmetric_non_transitive/dados.txt rename to tests/fixtures/symmetric_non_transitive/input.txt diff --git a/tests/fixtures/unknown_element/dados.txt b/tests/fixtures/unknown_element/input.txt similarity index 100% rename from tests/fixtures/unknown_element/dados.txt rename to tests/fixtures/unknown_element/input.txt diff --git a/tests/run_tests.sh b/tests/run_tests.sh index ffaff4d..75ed1a8 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -159,7 +159,7 @@ run_failure duplicate_element 'elementos duplicados' run_failure unknown_element 'elemento desconhecido' run_failure malformed_pair 'par ordenado invalido' run_failure invalid_count 'numero de elementos invalido' -run_failure missing_file 'nao foi possivel abrir o arquivo dados.txt' +run_failure missing_file 'nao foi possivel abrir o arquivo input.txt' printf '\n%d passed, %d failed\n' "${passed}" "${failed}" ((failed == 0)) diff --git a/tests/test_relation.c b/tests/test_relation.c new file mode 100644 index 0000000..9b2551f --- /dev/null +++ b/tests/test_relation.c @@ -0,0 +1,100 @@ +#include +#include +#include +#include + +#include "relation.h" + +static void test_empty_relation(void) { + Relation *relation = relation_create(NULL, 0); + + assert(relation != NULL); + assert(relation_size(relation) == 0); + assert(relation_pair_count(relation) == 0); + assert(!relation_contains(relation, 0, 0)); + relation_destroy(relation); +} + +static void test_invalid_elements(void) { + const int duplicates[] = {10, 10}; + const int element = 10; + + assert(relation_create(NULL, 1) == NULL); + assert(relation_create(duplicates, 2) == NULL); + assert(relation_create(&element, SIZE_MAX) == NULL); + assert(relation_clone(NULL) == NULL); +} + +static void test_dynamic_element_storage(void) { + int elements[64]; + Relation *relation; + size_t index; + + for (index = 0; index < 64; index++) { + elements[index] = (int)index; + } + + relation = relation_create(elements, 64); + assert(relation != NULL); + assert(relation_size(relation) == 64); + assert(relation_add_pair_at(relation, 63, 63)); + assert(relation_contains(relation, 63, 63)); + relation_destroy(relation); +} + +static void test_elements_and_pairs(void) { + const int elements[] = {10, -20, 30}; + Relation *relation = relation_create(elements, 3); + size_t index = 99; + int element = 0; + + assert(relation != NULL); + assert(relation_size(relation) == 3); + assert(relation_element_at(relation, 1, &element)); + assert(element == -20); + assert(!relation_element_at(relation, 3, &element)); + assert(relation_index_of(relation, 30, &index)); + assert(index == 2); + assert(!relation_index_of(relation, 99, &index)); + + assert(relation_add_pair(relation, 10, -20)); + assert(relation_contains(relation, 0, 1)); + assert(relation_pair_count(relation) == 1); + assert(relation_add_pair(relation, 10, -20)); + assert(relation_pair_count(relation) == 1); + assert(!relation_add_pair(relation, 10, 99)); + assert(!relation_add_pair_at(relation, 3, 0)); + + relation_destroy(relation); +} + +static void test_clone_is_independent(void) { + const int elements[] = {1, 2}; + Relation *original = relation_create(elements, 2); + Relation *clone; + + assert(original != NULL); + assert(relation_add_pair_at(original, 0, 1)); + clone = relation_clone(original); + assert(clone != NULL); + assert(relation_contains(clone, 0, 1)); + assert(relation_pair_count(clone) == 1); + + assert(relation_add_pair_at(clone, 1, 0)); + assert(relation_pair_count(clone) == 2); + assert(!relation_contains(original, 1, 0)); + assert(relation_pair_count(original) == 1); + + relation_destroy(clone); + relation_destroy(original); +} + +int main(void) { + test_empty_relation(); + test_invalid_elements(); + test_dynamic_element_storage(); + test_elements_and_pairs(); + test_clone_is_independent(); + relation_destroy(NULL); + return 0; +} From f2d19935ffbb8e689775b0e8dc2abdeadcf7ae3f Mon Sep 17 00:00:00 2001 From: Lucas Mariz Date: Sat, 12 Sep 2026 11:36:02 -0300 Subject: [PATCH 3/6] refactor: modularize relation analysis --- .github/workflows/ci.yml | 47 ++++ .gitignore | 2 + Makefile | 24 +- include/relation_analysis.h | 21 ++ include/relation_closure.h | 10 + include/relation_io.h | 8 + include/relation_report.h | 11 + src/application.c | 462 +----------------------------------- src/relation_analysis.c | 72 ++++++ src/relation_closure.c | 63 +++++ src/relation_io.c | 183 ++++++++++++++ src/relation_report.c | 216 +++++++++++++++++ tests/run_tests.sh | 122 +++++----- tests/test_analysis.c | 132 +++++++++++ 14 files changed, 855 insertions(+), 518 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 include/relation_analysis.h create mode 100644 include/relation_closure.h create mode 100644 include/relation_io.h create mode 100644 include/relation_report.h create mode 100644 src/relation_analysis.c create mode 100644 src/relation_closure.c create mode 100644 src/relation_io.c create mode 100644 src/relation_report.c create mode 100644 tests/test_analysis.c diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..123f462 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,47 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + build-and-test: + name: Build and test (${{ matrix.compiler }}) + runs-on: ubuntu-latest + timeout-minutes: 10 + strategy: + fail-fast: false + matrix: + compiler: + - gcc + - clang + env: + CC: ${{ matrix.compiler }} + steps: + - name: Check out repository + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Build and run tests + run: make test + + sanitizers: + name: Address and undefined behavior sanitizers + runs-on: ubuntu-latest + timeout-minutes: 10 + env: + CC: gcc + ASAN_DETECT_LEAKS: "1" + steps: + - name: Check out repository + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Build and run sanitized tests + run: make sanitize diff --git a/.gitignore b/.gitignore index ea673b8..2903561 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ /a.out-sanitize /relation-tests /relation-tests-sanitize +/analysis-tests +/analysis-tests-sanitize *.o *.gcda *.gcno diff --git a/Makefile b/Makefile index bd5778e..344f48f 100644 --- a/Makefile +++ b/Makefile @@ -12,9 +12,13 @@ TARGET := a.out SANITIZER_TARGET := a.out-sanitize RELATION_TEST_TARGET := relation-tests SANITIZER_RELATION_TEST_TARGET := relation-tests-sanitize -APP_SOURCES := src/main.c src/application.c src/relation.c +ANALYSIS_TEST_TARGET := analysis-tests +SANITIZER_ANALYSIS_TEST_TARGET := analysis-tests-sanitize +APP_SOURCES := src/main.c src/application.c src/relation.c src/relation_analysis.c \ + src/relation_closure.c src/relation_io.c src/relation_report.c RELATION_SOURCE := src/relation.c -HEADERS := include/application.h include/relation.h +ANALYSIS_SOURCES := src/relation.c src/relation_analysis.c src/relation_closure.c +HEADERS := $(wildcard include/*.h) .PHONY: all test sanitize clean @@ -33,13 +37,23 @@ $(RELATION_TEST_TARGET): tests/test_relation.c $(RELATION_SOURCE) include/relati $(SANITIZER_RELATION_TEST_TARGET): tests/test_relation.c $(RELATION_SOURCE) include/relation.h $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(SANITIZER_FLAGS) tests/test_relation.c $(RELATION_SOURCE) $(LDFLAGS) $(LDLIBS) -o $@ -test: $(TARGET) $(RELATION_TEST_TARGET) +$(ANALYSIS_TEST_TARGET): tests/test_analysis.c $(ANALYSIS_SOURCES) $(HEADERS) + $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(CFLAGS) tests/test_analysis.c $(ANALYSIS_SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ + +$(SANITIZER_ANALYSIS_TEST_TARGET): tests/test_analysis.c $(ANALYSIS_SOURCES) $(HEADERS) + $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(SANITIZER_FLAGS) tests/test_analysis.c $(ANALYSIS_SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ + +test: $(TARGET) $(RELATION_TEST_TARGET) $(ANALYSIS_TEST_TARGET) ./$(RELATION_TEST_TARGET) + ./$(ANALYSIS_TEST_TARGET) ./tests/run_tests.sh ./$(TARGET) -sanitize: $(SANITIZER_TARGET) $(SANITIZER_RELATION_TEST_TARGET) +sanitize: $(SANITIZER_TARGET) $(SANITIZER_RELATION_TEST_TARGET) $(SANITIZER_ANALYSIS_TEST_TARGET) ASAN_OPTIONS=detect_leaks=$${ASAN_DETECT_LEAKS:-0} ./$(SANITIZER_RELATION_TEST_TARGET) + ASAN_OPTIONS=detect_leaks=$${ASAN_DETECT_LEAKS:-0} ./$(SANITIZER_ANALYSIS_TEST_TARGET) ASAN_OPTIONS=detect_leaks=$${ASAN_DETECT_LEAKS:-0} ./tests/run_tests.sh ./$(SANITIZER_TARGET) clean: - rm -f $(TARGET) $(SANITIZER_TARGET) $(RELATION_TEST_TARGET) $(SANITIZER_RELATION_TEST_TARGET) + rm -f $(TARGET) $(SANITIZER_TARGET) $(RELATION_TEST_TARGET) \ + $(SANITIZER_RELATION_TEST_TARGET) $(ANALYSIS_TEST_TARGET) \ + $(SANITIZER_ANALYSIS_TEST_TARGET) diff --git a/include/relation_analysis.h b/include/relation_analysis.h new file mode 100644 index 0000000..dca7e66 --- /dev/null +++ b/include/relation_analysis.h @@ -0,0 +1,21 @@ +#ifndef BINARY_RELATION_ANALYZER_RELATION_ANALYSIS_H +#define BINARY_RELATION_ANALYZER_RELATION_ANALYSIS_H + +#include + +#include "relation.h" + +typedef struct { + bool reflexive; + bool irreflexive; + bool symmetric; + bool antisymmetric; + bool asymmetric; + bool transitive; + bool equivalence; + bool partial_order; +} RelationAnalysis; + +RelationAnalysis relation_analyze(const Relation *relation); + +#endif diff --git a/include/relation_closure.h b/include/relation_closure.h new file mode 100644 index 0000000..8bdf59c --- /dev/null +++ b/include/relation_closure.h @@ -0,0 +1,10 @@ +#ifndef BINARY_RELATION_ANALYZER_RELATION_CLOSURE_H +#define BINARY_RELATION_ANALYZER_RELATION_CLOSURE_H + +#include "relation.h" + +Relation *relation_reflexive_closure(const Relation *relation); +Relation *relation_symmetric_closure(const Relation *relation); +Relation *relation_transitive_closure(const Relation *relation); + +#endif diff --git a/include/relation_io.h b/include/relation_io.h new file mode 100644 index 0000000..6bec261 --- /dev/null +++ b/include/relation_io.h @@ -0,0 +1,8 @@ +#ifndef BINARY_RELATION_ANALYZER_RELATION_IO_H +#define BINARY_RELATION_ANALYZER_RELATION_IO_H + +#include "relation.h" + +Relation *relation_load_from_file(const char *filename); + +#endif diff --git a/include/relation_report.h b/include/relation_report.h new file mode 100644 index 0000000..6b52bd1 --- /dev/null +++ b/include/relation_report.h @@ -0,0 +1,11 @@ +#ifndef BINARY_RELATION_ANALYZER_RELATION_REPORT_H +#define BINARY_RELATION_ANALYZER_RELATION_REPORT_H + +#include +#include + +#include "relation.h" + +bool relation_write_report(const Relation *relation, FILE *output); + +#endif diff --git a/src/application.c b/src/application.c index 6f0269d..76b9de3 100644 --- a/src/application.c +++ b/src/application.c @@ -1,470 +1,28 @@ -#include -#include -#include #include #include #include -#include #include "application.h" #include "relation.h" +#include "relation_io.h" +#include "relation_report.h" #define INPUT_FILE "input.txt" -#define INPUT_BUFFER_SIZE 4096 -#define MAX_INPUT_ELEMENTS 50 - -typedef struct { - bool reflexive; - bool irreflexive; - bool symmetric; - bool antisymmetric; - bool asymmetric; - bool transitive; - bool equivalence; - bool partial_order; -} RelationAnalysis; - -static bool parse_next_integer(const char **cursor, int *value) { - char *end; - long number; - - while (isspace((unsigned char)**cursor)) { - (*cursor)++; - } - if (**cursor == '\0') { - return false; - } - - errno = 0; - number = strtol(*cursor, &end, 10); - if (end == *cursor || errno == ERANGE || number < INT_MIN || number > INT_MAX) { - return false; - } - - *value = (int)number; - *cursor = end; - return true; -} - -static bool contains_only_whitespace(const char *cursor) { - while (isspace((unsigned char)*cursor)) { - cursor++; - } - return *cursor == '\0'; -} - -static bool line_exceeds_buffer(const char *line, FILE *input) { - return strchr(line, '\n') == NULL && !feof(input); -} - -static bool contains_element(const int *elements, size_t element_count, int element) { - size_t index; - - for (index = 0; index < element_count; index++) { - if (elements[index] == element) { - return true; - } - } - return false; -} - -static Relation *load_relation(const char *filename) { - FILE *input = fopen(filename, "r"); - Relation *relation = NULL; - char line[INPUT_BUFFER_SIZE]; - const char *cursor; - int declared_count; - int elements[MAX_INPUT_ELEMENTS]; - size_t line_number = 1; - size_t index; - - if (input == NULL) { - fprintf(stderr, "Erro, nao foi possivel abrir o arquivo %s\n", filename); - return NULL; - } - - if (fgets(line, sizeof(line), input) == NULL || line_exceeds_buffer(line, input)) { - fprintf(stderr, "Erro de entrada na linha 1: cabecalho ausente ou muito longo\n"); - goto error; - } - - cursor = line; - if (!parse_next_integer(&cursor, &declared_count) || declared_count < 0 || - declared_count > MAX_INPUT_ELEMENTS) { - fprintf(stderr, "Erro de entrada na linha 1: numero de elementos invalido\n"); - goto error; - } - - for (index = 0; index < (size_t)declared_count; index++) { - if (!parse_next_integer(&cursor, &elements[index])) { - fprintf(stderr, "Erro de entrada na linha 1: quantidade de elementos incorreta\n"); - goto error; - } - if (contains_element(elements, index, elements[index])) { - fprintf(stderr, "Erro de entrada na linha 1: elementos duplicados\n"); - goto error; - } - } - - if (!contains_only_whitespace(cursor)) { - fprintf(stderr, "Erro de entrada na linha 1: quantidade de elementos incorreta\n"); - goto error; - } - - relation = relation_create(elements, (size_t)declared_count); - if (relation == NULL) { - fprintf(stderr, "Erro, nao foi possivel alocar a relacao\n"); - goto error; - } - - while (fgets(line, sizeof(line), input) != NULL) { - int source; - int target; - - line_number++; - if (line_exceeds_buffer(line, input)) { - fprintf(stderr, "Erro de entrada na linha %zu: linha muito longa\n", line_number); - goto error; - } - - cursor = line; - while (isspace((unsigned char)*cursor)) { - cursor++; - } - if (*cursor == '\0') { - continue; - } - - if (!parse_next_integer(&cursor, &source) || !parse_next_integer(&cursor, &target) || - !contains_only_whitespace(cursor)) { - fprintf(stderr, "Erro de entrada na linha %zu: par ordenado invalido\n", line_number); - goto error; - } - - if (!relation_add_pair(relation, source, target)) { - fprintf(stderr, "Erro de entrada na linha %zu: elemento desconhecido\n", line_number); - goto error; - } - } - - if (ferror(input)) { - fprintf(stderr, "Erro durante a leitura de %s\n", filename); - goto error; - } - - fclose(input); - return relation; - -error: - fclose(input); - relation_destroy(relation); - return NULL; -} - -static int element_at(const Relation *relation, size_t index) { - int element = 0; - - (void)relation_element_at(relation, index, &element); - return element; -} - -static Relation *create_transitive_closure(const Relation *relation) { - Relation *closure = relation_clone(relation); - size_t size = relation_size(relation); - size_t intermediate; - size_t source; - size_t target; - - if (closure == NULL) { - return NULL; - } - - for (intermediate = 0; intermediate < size; intermediate++) { - for (source = 0; source < size; source++) { - if (!relation_contains(closure, source, intermediate)) { - continue; - } - for (target = 0; target < size; target++) { - if (relation_contains(closure, intermediate, target)) { - (void)relation_add_pair_at(closure, source, target); - } - } - } - } - return closure; -} - -static RelationAnalysis analyze_relation(const Relation *relation, - const Relation *transitive_closure) { - RelationAnalysis analysis = {true, true, true, true, true, true, false, false}; - size_t size = relation_size(relation); - size_t source; - size_t target; - - for (source = 0; source < size; source++) { - if (relation_contains(relation, source, source)) { - analysis.irreflexive = false; - analysis.asymmetric = false; - } else { - analysis.reflexive = false; - } - - for (target = source + 1; target < size; target++) { - bool forward = relation_contains(relation, source, target); - bool reverse = relation_contains(relation, target, source); - - if (forward != reverse) { - analysis.symmetric = false; - } - if (forward && reverse) { - analysis.antisymmetric = false; - analysis.asymmetric = false; - } - } - } - - for (source = 0; source < size; source++) { - for (target = 0; target < size; target++) { - if (relation_contains(transitive_closure, source, target) && - !relation_contains(relation, source, target)) { - analysis.transitive = false; - } - } - } - - analysis.equivalence = analysis.reflexive && analysis.symmetric && analysis.transitive; - analysis.partial_order = analysis.reflexive && analysis.antisymmetric && analysis.transitive; - return analysis; -} - -static void print_matrix(const Relation *relation) { - size_t size = relation_size(relation); - size_t row; - size_t column; - - printf("\n\t"); - for (column = 0; column < size; column++) { - printf("%d ", element_at(relation, column)); - } - - printf("\n\n\n"); - for (row = 0; row < size; row++) { - printf("%d\t", element_at(relation, row)); - for (column = 0; column < size; column++) { - printf("%d ", relation_contains(relation, row, column) ? 1 : 0); - } - printf("\n"); - } - printf("\n\n"); -} - -static void print_reflexivity(const Relation *relation, bool reflexive) { - size_t index; - - printf("1. Reflexiva: %c\n", reflexive ? 'V' : 'F'); - if (reflexive) { - return; - } - - for (index = 0; index < relation_size(relation); index++) { - if (!relation_contains(relation, index, index)) { - printf("(%d,%d); ", element_at(relation, index), element_at(relation, index)); - } - } - printf("\n"); -} - -static void print_irreflexivity(const Relation *relation, bool irreflexive) { - size_t index; - - printf("2. Irreflexiva: %c\n", irreflexive ? 'V' : 'F'); - if (irreflexive) { - return; - } - - for (index = 0; index < relation_size(relation); index++) { - if (relation_contains(relation, index, index)) { - printf("(%d,%d); ", element_at(relation, index), element_at(relation, index)); - } - } - printf("\n"); -} - -static void print_symmetry(const Relation *relation, bool symmetric) { - size_t source; - size_t target; - - printf("3. Simetrica: %c\n", symmetric ? 'V' : 'F'); - if (symmetric) { - return; - } - - for (source = 0; source < relation_size(relation); source++) { - for (target = 0; target < relation_size(relation); target++) { - if (relation_contains(relation, source, target) && - !relation_contains(relation, target, source)) { - printf("(%d,%d) e (%d,%d); ", element_at(relation, source), - element_at(relation, target), element_at(relation, target), - element_at(relation, source)); - } - } - } - printf("\n"); -} - -static void print_antisymmetry(const Relation *relation, bool antisymmetric) { - size_t source; - size_t target; - - printf("4. Anti-simetrica: %c\n", antisymmetric ? 'V' : 'F'); - if (antisymmetric) { - return; - } - - for (source = 0; source < relation_size(relation); source++) { - for (target = source + 1; target < relation_size(relation); target++) { - if (relation_contains(relation, source, target) && - relation_contains(relation, target, source)) { - printf("(%d,%d) e (%d,%d); ", element_at(relation, source), - element_at(relation, target), element_at(relation, target), - element_at(relation, source)); - } - } - } - printf("\n"); -} - -static void print_transitivity(const Relation *relation, const Relation *closure, - bool transitive) { - size_t source; - size_t target; - - printf("6. Transitiva: %c\n", transitive ? 'V' : 'F'); - if (transitive) { - return; - } - - for (source = 0; source < relation_size(relation); source++) { - for (target = 0; target < relation_size(relation); target++) { - if (relation_contains(closure, source, target) && - !relation_contains(relation, source, target)) { - printf("(%d,%d); ", element_at(relation, source), - element_at(relation, target)); - } - } - } - printf("\n"); -} - -static void print_pair(int source, int target, bool *first) { - if (!*first) { - printf(","); - } - printf("(%d,%d)", source, target); - *first = false; -} - -static void print_existing_pairs(const Relation *relation, bool *first) { - size_t source; - size_t target; - - for (source = 0; source < relation_size(relation); source++) { - for (target = 0; target < relation_size(relation); target++) { - if (relation_contains(relation, source, target)) { - print_pair(element_at(relation, source), element_at(relation, target), first); - } - } - } -} - -static void print_reflexive_closure(const Relation *relation) { - bool first = true; - size_t index; - - printf("Fecho reflexivo da relaçao = {"); - print_existing_pairs(relation, &first); - for (index = 0; index < relation_size(relation); index++) { - if (!relation_contains(relation, index, index)) { - print_pair(element_at(relation, index), element_at(relation, index), &first); - } - } - printf("}\n"); -} - -static void print_symmetric_closure(const Relation *relation) { - bool first = true; - size_t source; - size_t target; - - printf("Fecho simetrico da relaçao = {"); - print_existing_pairs(relation, &first); - for (source = 0; source < relation_size(relation); source++) { - for (target = 0; target < relation_size(relation); target++) { - if (relation_contains(relation, source, target) && - !relation_contains(relation, target, source)) { - print_pair(element_at(relation, target), element_at(relation, source), &first); - } - } - } - printf("}\n"); -} - -static void print_transitive_closure(const Relation *relation, const Relation *closure) { - bool first = true; - size_t source; - size_t target; - - printf("Fecho transitivo da relaçao = {"); - print_existing_pairs(relation, &first); - for (source = 0; source < relation_size(relation); source++) { - for (target = 0; target < relation_size(relation); target++) { - if (relation_contains(closure, source, target) && - !relation_contains(relation, source, target)) { - print_pair(element_at(relation, source), element_at(relation, target), &first); - } - } - } - printf("}\n"); -} - -static bool print_report(const Relation *relation) { - Relation *transitive_closure = create_transitive_closure(relation); - RelationAnalysis analysis; - - if (transitive_closure == NULL) { - fprintf(stderr, "Erro, nao foi possivel calcular o fecho transitivo\n"); - return false; - } - - analysis = analyze_relation(relation, transitive_closure); - printf("\n\nPropriedades:\n\n"); - print_matrix(relation); - print_reflexivity(relation, analysis.reflexive); - print_irreflexivity(relation, analysis.irreflexive); - print_symmetry(relation, analysis.symmetric); - print_antisymmetry(relation, analysis.antisymmetric); - printf("5. Asimetrica: %c\n", analysis.asymmetric ? 'V' : 'F'); - print_transitivity(relation, transitive_closure, analysis.transitive); - printf("\nRelaçao de equivalencia: %c\n", analysis.equivalence ? 'V' : 'F'); - printf("Relaçao de ordem parcial: %c\n\n", analysis.partial_order ? 'V' : 'F'); - print_reflexive_closure(relation); - print_symmetric_closure(relation); - print_transitive_closure(relation, transitive_closure); - printf("\n"); - - relation_destroy(transitive_closure); - return true; -} int run_application(void) { - Relation *relation = load_relation(INPUT_FILE); + Relation *relation = relation_load_from_file(INPUT_FILE); bool success; if (relation == NULL) { return EXIT_FAILURE; } - success = print_report(relation); + success = relation_write_report(relation, stdout); relation_destroy(relation); - return success ? EXIT_SUCCESS : EXIT_FAILURE; + + if (!success) { + fputs("Error: could not produce the relation report\n", stderr); + return EXIT_FAILURE; + } + return EXIT_SUCCESS; } diff --git a/src/relation_analysis.c b/src/relation_analysis.c new file mode 100644 index 0000000..7284298 --- /dev/null +++ b/src/relation_analysis.c @@ -0,0 +1,72 @@ +#include +#include + +#include "relation_analysis.h" + +static bool is_transitive(const Relation *relation) { + size_t size = relation_size(relation); + size_t source; + size_t intermediate; + size_t target; + + for (source = 0; source < size; source++) { + for (intermediate = 0; intermediate < size; intermediate++) { + if (!relation_contains(relation, source, intermediate)) { + continue; + } + for (target = 0; target < size; target++) { + if (relation_contains(relation, intermediate, target) && + !relation_contains(relation, source, target)) { + return false; + } + } + } + } + return true; +} + +RelationAnalysis relation_analyze(const Relation *relation) { + RelationAnalysis analysis = {0}; + size_t size; + size_t source; + size_t target; + + if (relation == NULL) { + return analysis; + } + + analysis.reflexive = true; + analysis.irreflexive = true; + analysis.symmetric = true; + analysis.antisymmetric = true; + analysis.asymmetric = true; + analysis.transitive = true; + size = relation_size(relation); + + for (source = 0; source < size; source++) { + if (relation_contains(relation, source, source)) { + analysis.irreflexive = false; + analysis.asymmetric = false; + } else { + analysis.reflexive = false; + } + + for (target = source + 1; target < size; target++) { + bool forward = relation_contains(relation, source, target); + bool reverse = relation_contains(relation, target, source); + + if (forward != reverse) { + analysis.symmetric = false; + } + if (forward && reverse) { + analysis.antisymmetric = false; + analysis.asymmetric = false; + } + } + } + + analysis.transitive = is_transitive(relation); + analysis.equivalence = analysis.reflexive && analysis.symmetric && analysis.transitive; + analysis.partial_order = analysis.reflexive && analysis.antisymmetric && analysis.transitive; + return analysis; +} diff --git a/src/relation_closure.c b/src/relation_closure.c new file mode 100644 index 0000000..814f188 --- /dev/null +++ b/src/relation_closure.c @@ -0,0 +1,63 @@ +#include + +#include "relation_closure.h" + +Relation *relation_reflexive_closure(const Relation *relation) { + Relation *closure = relation_clone(relation); + size_t index; + + if (closure == NULL) { + return NULL; + } + + for (index = 0; index < relation_size(closure); index++) { + (void)relation_add_pair_at(closure, index, index); + } + return closure; +} + +Relation *relation_symmetric_closure(const Relation *relation) { + Relation *closure = relation_clone(relation); + size_t size = relation_size(relation); + size_t source; + size_t target; + + if (closure == NULL) { + return NULL; + } + + for (source = 0; source < size; source++) { + for (target = 0; target < size; target++) { + if (relation_contains(relation, source, target)) { + (void)relation_add_pair_at(closure, target, source); + } + } + } + return closure; +} + +Relation *relation_transitive_closure(const Relation *relation) { + Relation *closure = relation_clone(relation); + size_t size = relation_size(relation); + size_t intermediate; + size_t source; + size_t target; + + if (closure == NULL) { + return NULL; + } + + for (intermediate = 0; intermediate < size; intermediate++) { + for (source = 0; source < size; source++) { + if (!relation_contains(closure, source, intermediate)) { + continue; + } + for (target = 0; target < size; target++) { + if (relation_contains(closure, intermediate, target)) { + (void)relation_add_pair_at(closure, source, target); + } + } + } + } + return closure; +} diff --git a/src/relation_io.c b/src/relation_io.c new file mode 100644 index 0000000..4d8700c --- /dev/null +++ b/src/relation_io.c @@ -0,0 +1,183 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "relation_io.h" + +#define INPUT_BUFFER_SIZE 4096 +#define MAX_INPUT_ELEMENTS 50 + +static bool parse_next_integer(const char **cursor, int *value) { + char *end; + long number; + + while (isspace((unsigned char)**cursor)) { + (*cursor)++; + } + if (**cursor == '\0') { + return false; + } + + errno = 0; + number = strtol(*cursor, &end, 10); + if (end == *cursor || errno == ERANGE || number < INT_MIN || number > INT_MAX) { + return false; + } + + *value = (int)number; + *cursor = end; + return true; +} + +static bool contains_only_whitespace(const char *cursor) { + while (isspace((unsigned char)*cursor)) { + cursor++; + } + return *cursor == '\0'; +} + +static bool line_exceeds_buffer(const char *line, FILE *input) { + return strchr(line, '\n') == NULL && !feof(input); +} + +static bool contains_element(const int *elements, size_t element_count, int element) { + size_t index; + + for (index = 0; index < element_count; index++) { + if (elements[index] == element) { + return true; + } + } + return false; +} + +static bool read_header(FILE *input, int *elements, size_t *element_count) { + char line[INPUT_BUFFER_SIZE]; + const char *cursor; + int declared_count; + size_t index; + + if (fgets(line, sizeof(line), input) == NULL || line_exceeds_buffer(line, input)) { + fputs("Input error on line 1: missing or overlong header\n", stderr); + return false; + } + + cursor = line; + if (!parse_next_integer(&cursor, &declared_count) || declared_count < 0 || + declared_count > MAX_INPUT_ELEMENTS) { + fputs("Input error on line 1: invalid element count\n", stderr); + return false; + } + + for (index = 0; index < (size_t)declared_count; index++) { + if (!parse_next_integer(&cursor, &elements[index])) { + fputs("Input error on line 1: wrong number of elements\n", stderr); + return false; + } + if (contains_element(elements, index, elements[index])) { + fputs("Input error on line 1: duplicate elements\n", stderr); + return false; + } + } + + if (!contains_only_whitespace(cursor)) { + fputs("Input error on line 1: wrong number of elements\n", stderr); + return false; + } + + *element_count = (size_t)declared_count; + return true; +} + +static bool parse_pair_line(const char *line, int *source, int *target, bool *is_empty) { + const char *cursor = line; + + while (isspace((unsigned char)*cursor)) { + cursor++; + } + *is_empty = *cursor == '\0'; + if (*is_empty) { + return true; + } + return parse_next_integer(&cursor, source) && parse_next_integer(&cursor, target) && + contains_only_whitespace(cursor); +} + +static bool read_pairs(FILE *input, const char *filename, Relation *relation) { + char line[INPUT_BUFFER_SIZE]; + size_t line_number = 1; + + while (fgets(line, sizeof(line), input) != NULL) { + int source; + int target; + bool is_empty; + + line_number++; + if (line_exceeds_buffer(line, input)) { + fprintf(stderr, "Input error on line %zu: line is too long\n", line_number); + return false; + } + + if (!parse_pair_line(line, &source, &target, &is_empty)) { + fprintf(stderr, "Input error on line %zu: invalid ordered pair\n", line_number); + return false; + } + if (is_empty) { + continue; + } + + if (!relation_add_pair(relation, source, target)) { + fprintf(stderr, "Input error on line %zu: unknown element\n", line_number); + return false; + } + } + + if (ferror(input)) { + fprintf(stderr, "I/O error while reading %s\n", filename); + return false; + } + return true; +} + +Relation *relation_load_from_file(const char *filename) { + FILE *input; + Relation *relation; + int elements[MAX_INPUT_ELEMENTS]; + size_t element_count; + + if (filename == NULL) { + fputs("Error: no input filename was provided\n", stderr); + return NULL; + } + + input = fopen(filename, "r"); + if (input == NULL) { + fprintf(stderr, "Error: could not open input file %s\n", filename); + return NULL; + } + + if (!read_header(input, elements, &element_count)) { + fclose(input); + return NULL; + } + + relation = relation_create(elements, element_count); + if (relation == NULL) { + fputs("Error: could not allocate the relation\n", stderr); + fclose(input); + return NULL; + } + + if (!read_pairs(input, filename, relation)) { + relation_destroy(relation); + fclose(input); + return NULL; + } + + fclose(input); + return relation; +} diff --git a/src/relation_report.c b/src/relation_report.c new file mode 100644 index 0000000..5bec659 --- /dev/null +++ b/src/relation_report.c @@ -0,0 +1,216 @@ +#include +#include +#include + +#include "relation_analysis.h" +#include "relation_closure.h" +#include "relation_report.h" + +static const char *boolean_text(bool value) { + return value ? "true" : "false"; +} + +static int element_at(const Relation *relation, size_t index) { + int element = 0; + + (void)relation_element_at(relation, index, &element); + return element; +} + +static void write_matrix(const Relation *relation, FILE *output) { + size_t size = relation_size(relation); + size_t row; + size_t column; + + fputs("Relation matrix:\n\n\t", output); + for (column = 0; column < size; column++) { + fprintf(output, "%d ", element_at(relation, column)); + } + + fputs("\n\n", output); + for (row = 0; row < size; row++) { + fprintf(output, "%d\t", element_at(relation, row)); + for (column = 0; column < size; column++) { + fprintf(output, "%d ", relation_contains(relation, row, column) ? 1 : 0); + } + fputc('\n', output); + } + fputc('\n', output); +} + +static void write_reflexivity_details(const Relation *relation, FILE *output) { + size_t index; + + fputs("Missing reflexive pairs: ", output); + for (index = 0; index < relation_size(relation); index++) { + if (!relation_contains(relation, index, index)) { + fprintf(output, "(%d,%d); ", element_at(relation, index), + element_at(relation, index)); + } + } + fputc('\n', output); +} + +static void write_irreflexivity_details(const Relation *relation, FILE *output) { + size_t index; + + fputs("Irreflexive counterexamples: ", output); + for (index = 0; index < relation_size(relation); index++) { + if (relation_contains(relation, index, index)) { + fprintf(output, "(%d,%d); ", element_at(relation, index), + element_at(relation, index)); + } + } + fputc('\n', output); +} + +static void write_symmetry_details(const Relation *relation, FILE *output) { + size_t source; + size_t target; + + fputs("Missing symmetric pairs: ", output); + for (source = 0; source < relation_size(relation); source++) { + for (target = 0; target < relation_size(relation); target++) { + if (relation_contains(relation, source, target) && + !relation_contains(relation, target, source)) { + fprintf(output, "(%d,%d); ", element_at(relation, target), + element_at(relation, source)); + } + } + } + fputc('\n', output); +} + +static void write_antisymmetry_details(const Relation *relation, FILE *output) { + size_t source; + size_t target; + + fputs("Antisymmetric counterexamples: ", output); + for (source = 0; source < relation_size(relation); source++) { + for (target = source + 1; target < relation_size(relation); target++) { + if (relation_contains(relation, source, target) && + relation_contains(relation, target, source)) { + fprintf(output, "(%d,%d) and (%d,%d); ", element_at(relation, source), + element_at(relation, target), element_at(relation, target), + element_at(relation, source)); + } + } + } + fputc('\n', output); +} + +static void write_transitivity_details(const Relation *relation, const Relation *closure, + FILE *output) { + size_t source; + size_t target; + + fputs("Missing transitive pairs: ", output); + for (source = 0; source < relation_size(relation); source++) { + for (target = 0; target < relation_size(relation); target++) { + if (relation_contains(closure, source, target) && + !relation_contains(relation, source, target)) { + fprintf(output, "(%d,%d); ", element_at(relation, source), + element_at(relation, target)); + } + } + } + fputc('\n', output); +} + +static void write_pair(FILE *output, int source, int target, bool *first) { + if (!*first) { + fputc(',', output); + } + fprintf(output, "(%d,%d)", source, target); + *first = false; +} + +static void write_closure(const char *name, const Relation *relation, const Relation *closure, + FILE *output) { + bool first = true; + size_t source; + size_t target; + + fprintf(output, "%s closure = {", name); + for (source = 0; source < relation_size(relation); source++) { + for (target = 0; target < relation_size(relation); target++) { + if (relation_contains(relation, source, target)) { + write_pair(output, element_at(relation, source), element_at(relation, target), + &first); + } + } + } + + for (source = 0; source < relation_size(relation); source++) { + for (target = 0; target < relation_size(relation); target++) { + if (relation_contains(closure, source, target) && + !relation_contains(relation, source, target)) { + write_pair(output, element_at(relation, source), element_at(relation, target), + &first); + } + } + } + fputs("}\n", output); +} + +static void write_analysis(const Relation *relation, const Relation *transitive_closure, + const RelationAnalysis *analysis, FILE *output) { + fprintf(output, "1. Reflexive: %s\n", boolean_text(analysis->reflexive)); + if (!analysis->reflexive) { + write_reflexivity_details(relation, output); + } + fprintf(output, "2. Irreflexive: %s\n", boolean_text(analysis->irreflexive)); + if (!analysis->irreflexive) { + write_irreflexivity_details(relation, output); + } + fprintf(output, "3. Symmetric: %s\n", boolean_text(analysis->symmetric)); + if (!analysis->symmetric) { + write_symmetry_details(relation, output); + } + fprintf(output, "4. Antisymmetric: %s\n", boolean_text(analysis->antisymmetric)); + if (!analysis->antisymmetric) { + write_antisymmetry_details(relation, output); + } + fprintf(output, "5. Asymmetric: %s\n", boolean_text(analysis->asymmetric)); + fprintf(output, "6. Transitive: %s\n", boolean_text(analysis->transitive)); + if (!analysis->transitive) { + write_transitivity_details(relation, transitive_closure, output); + } + fprintf(output, "\nEquivalence relation: %s\n", boolean_text(analysis->equivalence)); + fprintf(output, "Partial order: %s\n\n", boolean_text(analysis->partial_order)); +} + +bool relation_write_report(const Relation *relation, FILE *output) { + RelationAnalysis analysis; + Relation *reflexive_closure; + Relation *symmetric_closure; + Relation *transitive_closure; + + if (relation == NULL || output == NULL) { + return false; + } + + reflexive_closure = relation_reflexive_closure(relation); + symmetric_closure = relation_symmetric_closure(relation); + transitive_closure = relation_transitive_closure(relation); + if (reflexive_closure == NULL || symmetric_closure == NULL || transitive_closure == NULL) { + relation_destroy(reflexive_closure); + relation_destroy(symmetric_closure); + relation_destroy(transitive_closure); + return false; + } + + analysis = relation_analyze(relation); + fputs("\nRelation properties:\n\n", output); + write_matrix(relation, output); + write_analysis(relation, transitive_closure, &analysis, output); + write_closure("Reflexive", relation, reflexive_closure, output); + write_closure("Symmetric", relation, symmetric_closure, output); + write_closure("Transitive", relation, transitive_closure, output); + fputc('\n', output); + + relation_destroy(reflexive_closure); + relation_destroy(symmetric_closure); + relation_destroy(transitive_closure); + return !ferror(output); +} diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 75ed1a8..9ed579a 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -82,84 +82,84 @@ run_failure() { } run_success legacy_sample \ - '1. Reflexiva: V' \ - '3. Simetrica: V' \ - '4. Anti-simetrica: F' \ - '6. Transitiva: V' \ - 'Relaçao de equivalencia: V' \ - 'Relaçao de ordem parcial: F' + '1. Reflexive: true' \ + '3. Symmetric: true' \ + '4. Antisymmetric: false' \ + '6. Transitive: true' \ + 'Equivalence relation: true' \ + 'Partial order: false' run_success empty_set \ - '1. Reflexiva: V' \ - '2. Irreflexiva: V' \ - '3. Simetrica: V' \ - '4. Anti-simetrica: V' \ - '5. Asimetrica: V' \ - '6. Transitiva: V' \ - 'Relaçao de equivalencia: V' \ - 'Relaçao de ordem parcial: V' \ - 'Fecho reflexivo da relaçao = {}' \ - 'Fecho simetrico da relaçao = {}' \ - 'Fecho transitivo da relaçao = {}' + '1. Reflexive: true' \ + '2. Irreflexive: true' \ + '3. Symmetric: true' \ + '4. Antisymmetric: true' \ + '5. Asymmetric: true' \ + '6. Transitive: true' \ + 'Equivalence relation: true' \ + 'Partial order: true' \ + 'Reflexive closure = {}' \ + 'Symmetric closure = {}' \ + 'Transitive closure = {}' run_success empty_relation \ - '1. Reflexiva: F' \ - '2. Irreflexiva: V' \ - '3. Simetrica: V' \ - '4. Anti-simetrica: V' \ - '5. Asimetrica: V' \ - '6. Transitiva: V' \ - 'Relaçao de equivalencia: F' \ - 'Relaçao de ordem parcial: F' \ - 'Fecho reflexivo da relaçao = {(1,1),(2,2)}' + '1. Reflexive: false' \ + '2. Irreflexive: true' \ + '3. Symmetric: true' \ + '4. Antisymmetric: true' \ + '5. Asymmetric: true' \ + '6. Transitive: true' \ + 'Equivalence relation: false' \ + 'Partial order: false' \ + 'Reflexive closure = {(1,1),(2,2)}' run_success equivalence_multi_digit \ - '1. Reflexiva: V' \ - '2. Irreflexiva: F' \ - '3. Simetrica: V' \ - '4. Anti-simetrica: F' \ - '5. Asimetrica: F' \ - '6. Transitiva: V' \ - 'Relaçao de equivalencia: V' \ - 'Relaçao de ordem parcial: F' + '1. Reflexive: true' \ + '2. Irreflexive: false' \ + '3. Symmetric: true' \ + '4. Antisymmetric: false' \ + '5. Asymmetric: false' \ + '6. Transitive: true' \ + 'Equivalence relation: true' \ + 'Partial order: false' run_success partial_order \ - '1. Reflexiva: V' \ - '3. Simetrica: F' \ - '4. Anti-simetrica: V' \ - '6. Transitiva: V' \ - 'Relaçao de equivalencia: F' \ - 'Relaçao de ordem parcial: V' + '1. Reflexive: true' \ + '3. Symmetric: false' \ + '4. Antisymmetric: true' \ + '6. Transitive: true' \ + 'Equivalence relation: false' \ + 'Partial order: true' run_success asymmetric_transitive \ - '1. Reflexiva: F' \ - '2. Irreflexiva: V' \ - '3. Simetrica: F' \ - '4. Anti-simetrica: V' \ - '5. Asimetrica: V' \ - '6. Transitiva: V' + '1. Reflexive: false' \ + '2. Irreflexive: true' \ + '3. Symmetric: false' \ + '4. Antisymmetric: true' \ + '5. Asymmetric: true' \ + '6. Transitive: true' run_success symmetric_non_transitive \ - '2. Irreflexiva: V' \ - '3. Simetrica: V' \ - '4. Anti-simetrica: F' \ - '5. Asimetrica: F' \ - '6. Transitiva: F' + '2. Irreflexive: true' \ + '3. Symmetric: true' \ + '4. Antisymmetric: false' \ + '5. Asymmetric: false' \ + '6. Transitive: false' run_success non_transitive_closure \ - '6. Transitiva: F' \ + '6. Transitive: false' \ '(1,3);' \ - 'Fecho transitivo da relaçao = {(1,2),(2,3),(1,3)}' + 'Transitive closure = {(1,2),(2,3),(1,3)}' run_success duplicate_pair \ - 'Fecho simetrico da relaçao = {(10,20),(20,10)}' - -run_failure malformed_header 'quantidade de elementos incorreta' -run_failure duplicate_element 'elementos duplicados' -run_failure unknown_element 'elemento desconhecido' -run_failure malformed_pair 'par ordenado invalido' -run_failure invalid_count 'numero de elementos invalido' -run_failure missing_file 'nao foi possivel abrir o arquivo input.txt' + 'Symmetric closure = {(10,20),(20,10)}' + +run_failure malformed_header 'wrong number of elements' +run_failure duplicate_element 'duplicate elements' +run_failure unknown_element 'unknown element' +run_failure malformed_pair 'invalid ordered pair' +run_failure invalid_count 'invalid element count' +run_failure missing_file 'could not open input file input.txt' printf '\n%d passed, %d failed\n' "${passed}" "${failed}" ((failed == 0)) diff --git a/tests/test_analysis.c b/tests/test_analysis.c new file mode 100644 index 0000000..aa42c85 --- /dev/null +++ b/tests/test_analysis.c @@ -0,0 +1,132 @@ +#include +#include + +#include "relation.h" +#include "relation_analysis.h" +#include "relation_closure.h" + +static Relation *create_relation(const int *elements, size_t element_count, + const int (*pairs)[2], size_t pair_count) { + Relation *relation = relation_create(elements, element_count); + size_t index; + + assert(relation != NULL); + for (index = 0; index < pair_count; index++) { + assert(relation_add_pair(relation, pairs[index][0], pairs[index][1])); + } + return relation; +} + +static void test_empty_set_properties(void) { + Relation *relation = relation_create(NULL, 0); + RelationAnalysis analysis; + + assert(relation != NULL); + analysis = relation_analyze(relation); + assert(analysis.reflexive); + assert(analysis.irreflexive); + assert(analysis.symmetric); + assert(analysis.antisymmetric); + assert(analysis.asymmetric); + assert(analysis.transitive); + assert(analysis.equivalence); + assert(analysis.partial_order); + relation_destroy(relation); +} + +static void test_equivalence_properties(void) { + const int elements[] = {10, 20}; + const int pairs[][2] = {{10, 10}, {10, 20}, {20, 10}, {20, 20}}; + Relation *relation = create_relation(elements, 2, pairs, 4); + RelationAnalysis analysis = relation_analyze(relation); + + assert(analysis.reflexive); + assert(!analysis.irreflexive); + assert(analysis.symmetric); + assert(!analysis.antisymmetric); + assert(!analysis.asymmetric); + assert(analysis.transitive); + assert(analysis.equivalence); + assert(!analysis.partial_order); + relation_destroy(relation); +} + +static void test_partial_order_properties(void) { + const int elements[] = {1, 2, 3}; + const int pairs[][2] = {{1, 1}, {2, 2}, {3, 3}, {1, 2}, {2, 3}, {1, 3}}; + Relation *relation = create_relation(elements, 3, pairs, 6); + RelationAnalysis analysis = relation_analyze(relation); + + assert(analysis.reflexive); + assert(!analysis.irreflexive); + assert(!analysis.symmetric); + assert(analysis.antisymmetric); + assert(!analysis.asymmetric); + assert(analysis.transitive); + assert(!analysis.equivalence); + assert(analysis.partial_order); + relation_destroy(relation); +} + +static void test_asymmetric_and_non_transitive_properties(void) { + const int elements[] = {1, 2, 3}; + const int asymmetric_pairs[][2] = {{1, 2}, {2, 3}, {1, 3}}; + const int non_transitive_pairs[][2] = {{1, 2}, {2, 3}}; + Relation *asymmetric = create_relation(elements, 3, asymmetric_pairs, 3); + Relation *non_transitive = create_relation(elements, 3, non_transitive_pairs, 2); + RelationAnalysis analysis = relation_analyze(asymmetric); + + assert(analysis.irreflexive); + assert(analysis.antisymmetric); + assert(analysis.asymmetric); + assert(analysis.transitive); + + analysis = relation_analyze(non_transitive); + assert(!analysis.transitive); + relation_destroy(asymmetric); + relation_destroy(non_transitive); +} + +static void test_closures_are_independent_and_minimal(void) { + const int elements[] = {1, 2, 3}; + const int pairs[][2] = {{1, 2}, {2, 3}}; + Relation *relation = create_relation(elements, 3, pairs, 2); + Relation *reflexive = relation_reflexive_closure(relation); + Relation *symmetric = relation_symmetric_closure(relation); + Relation *transitive = relation_transitive_closure(relation); + + assert(reflexive != NULL); + assert(symmetric != NULL); + assert(transitive != NULL); + + assert(relation_pair_count(reflexive) == 5); + assert(relation_contains(reflexive, 0, 0)); + assert(relation_contains(reflexive, 1, 1)); + assert(relation_contains(reflexive, 2, 2)); + + assert(relation_pair_count(symmetric) == 4); + assert(relation_contains(symmetric, 1, 0)); + assert(relation_contains(symmetric, 2, 1)); + + assert(relation_pair_count(transitive) == 3); + assert(relation_contains(transitive, 0, 2)); + + assert(relation_pair_count(relation) == 2); + assert(!relation_contains(relation, 0, 0)); + assert(!relation_contains(relation, 1, 0)); + assert(!relation_contains(relation, 0, 2)); + + relation_destroy(reflexive); + relation_destroy(symmetric); + relation_destroy(transitive); + relation_destroy(relation); +} + +int main(void) { + test_empty_set_properties(); + test_equivalence_properties(); + test_partial_order_properties(); + test_asymmetric_and_non_transitive_properties(); + test_closures_are_independent_and_minimal(); + return 0; +} From 388954a9fdbdaed742b7e37e01f1127bc7b880f4 Mon Sep 17 00:00:00 2001 From: Lucas Mariz Date: Sat, 12 Sep 2026 11:48:07 -0300 Subject: [PATCH 4/6] feat: add command-line interface and exhaustive tests --- .gitignore | 8 +- Makefile | 36 ++- input.txt => examples/sample.txt | 0 include/application.h | 3 +- include/relation.h | 23 ++ include/relation_analysis.h | 6 + include/relation_closure.h | 5 + include/relation_io.h | 4 + include/relation_report.h | 1 + src/application.c | 44 +++- src/main.c | 4 +- src/relation.c | 3 + src/relation_analysis.c | 1 + src/relation_closure.c | 1 + src/relation_io.c | 1 + src/relation_report.c | 1 + tests/fixtures/blank_header/input.txt | 1 + tests/fixtures/extra_header_element/input.txt | 1 + tests/fixtures/incomplete_pair/input.txt | 2 + tests/fixtures/integer_boundaries/input.txt | 2 + tests/fixtures/invalid_count_token/input.txt | 1 + tests/fixtures/invalid_pair_token/input.txt | 2 + .../fixtures/maximum_element_count/input.txt | 1 + tests/fixtures/negative_count/input.txt | 1 + .../nonadjacent_duplicate_element/input.txt | 1 + tests/fixtures/overflowing_element/input.txt | 1 + tests/fixtures/overflowing_pair/input.txt | 2 + tests/fixtures/unknown_source/input.txt | 2 + .../whitespace_and_blank_lines/input.txt | 4 + tests/run_tests.sh | 123 +++++++++- tests/test_analysis.c | 48 ++++ tests/test_exhaustive.c | 222 ++++++++++++++++++ tests/test_io_report.c | 59 +++++ tests/test_relation.c | 48 +++- 34 files changed, 644 insertions(+), 18 deletions(-) rename input.txt => examples/sample.txt (100%) create mode 100644 tests/fixtures/blank_header/input.txt create mode 100644 tests/fixtures/extra_header_element/input.txt create mode 100644 tests/fixtures/incomplete_pair/input.txt create mode 100644 tests/fixtures/integer_boundaries/input.txt create mode 100644 tests/fixtures/invalid_count_token/input.txt create mode 100644 tests/fixtures/invalid_pair_token/input.txt create mode 100644 tests/fixtures/maximum_element_count/input.txt create mode 100644 tests/fixtures/negative_count/input.txt create mode 100644 tests/fixtures/nonadjacent_duplicate_element/input.txt create mode 100644 tests/fixtures/overflowing_element/input.txt create mode 100644 tests/fixtures/overflowing_pair/input.txt create mode 100644 tests/fixtures/unknown_source/input.txt create mode 100644 tests/fixtures/whitespace_and_blank_lines/input.txt create mode 100644 tests/test_exhaustive.c create mode 100644 tests/test_io_report.c diff --git a/.gitignore b/.gitignore index 2903561..63716a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,13 @@ -/a.out -/a.out-sanitize +/binary-relation-analyzer +/binary-relation-analyzer-sanitize /relation-tests /relation-tests-sanitize /analysis-tests /analysis-tests-sanitize +/exhaustive-tests +/exhaustive-tests-sanitize +/io-report-tests +/io-report-tests-sanitize *.o *.gcda *.gcno diff --git a/Makefile b/Makefile index 344f48f..43f7341 100644 --- a/Makefile +++ b/Makefile @@ -8,16 +8,21 @@ STANDARD_FLAGS := -std=c17 WARNING_FLAGS := -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Werror SANITIZER_FLAGS := -g -O1 -fno-omit-frame-pointer -fsanitize=address,undefined -TARGET := a.out -SANITIZER_TARGET := a.out-sanitize +TARGET := binary-relation-analyzer +SANITIZER_TARGET := binary-relation-analyzer-sanitize RELATION_TEST_TARGET := relation-tests SANITIZER_RELATION_TEST_TARGET := relation-tests-sanitize ANALYSIS_TEST_TARGET := analysis-tests SANITIZER_ANALYSIS_TEST_TARGET := analysis-tests-sanitize +EXHAUSTIVE_TEST_TARGET := exhaustive-tests +SANITIZER_EXHAUSTIVE_TEST_TARGET := exhaustive-tests-sanitize +IO_REPORT_TEST_TARGET := io-report-tests +SANITIZER_IO_REPORT_TEST_TARGET := io-report-tests-sanitize APP_SOURCES := src/main.c src/application.c src/relation.c src/relation_analysis.c \ src/relation_closure.c src/relation_io.c src/relation_report.c RELATION_SOURCE := src/relation.c ANALYSIS_SOURCES := src/relation.c src/relation_analysis.c src/relation_closure.c +IO_REPORT_SOURCES := $(ANALYSIS_SOURCES) src/relation_io.c src/relation_report.c HEADERS := $(wildcard include/*.h) .PHONY: all test sanitize clean @@ -43,17 +48,38 @@ $(ANALYSIS_TEST_TARGET): tests/test_analysis.c $(ANALYSIS_SOURCES) $(HEADERS) $(SANITIZER_ANALYSIS_TEST_TARGET): tests/test_analysis.c $(ANALYSIS_SOURCES) $(HEADERS) $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(SANITIZER_FLAGS) tests/test_analysis.c $(ANALYSIS_SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ -test: $(TARGET) $(RELATION_TEST_TARGET) $(ANALYSIS_TEST_TARGET) +$(EXHAUSTIVE_TEST_TARGET): tests/test_exhaustive.c $(ANALYSIS_SOURCES) $(HEADERS) + $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(CFLAGS) tests/test_exhaustive.c $(ANALYSIS_SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ + +$(SANITIZER_EXHAUSTIVE_TEST_TARGET): tests/test_exhaustive.c $(ANALYSIS_SOURCES) $(HEADERS) + $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(SANITIZER_FLAGS) tests/test_exhaustive.c $(ANALYSIS_SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ + +$(IO_REPORT_TEST_TARGET): tests/test_io_report.c $(IO_REPORT_SOURCES) $(HEADERS) + $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(CFLAGS) tests/test_io_report.c $(IO_REPORT_SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ + +$(SANITIZER_IO_REPORT_TEST_TARGET): tests/test_io_report.c $(IO_REPORT_SOURCES) $(HEADERS) + $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(SANITIZER_FLAGS) tests/test_io_report.c $(IO_REPORT_SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ + +test: $(TARGET) $(RELATION_TEST_TARGET) $(ANALYSIS_TEST_TARGET) $(EXHAUSTIVE_TEST_TARGET) \ + $(IO_REPORT_TEST_TARGET) ./$(RELATION_TEST_TARGET) ./$(ANALYSIS_TEST_TARGET) + ./$(EXHAUSTIVE_TEST_TARGET) + ./$(IO_REPORT_TEST_TARGET) ./tests/run_tests.sh ./$(TARGET) -sanitize: $(SANITIZER_TARGET) $(SANITIZER_RELATION_TEST_TARGET) $(SANITIZER_ANALYSIS_TEST_TARGET) +sanitize: $(SANITIZER_TARGET) $(SANITIZER_RELATION_TEST_TARGET) \ + $(SANITIZER_ANALYSIS_TEST_TARGET) $(SANITIZER_EXHAUSTIVE_TEST_TARGET) \ + $(SANITIZER_IO_REPORT_TEST_TARGET) ASAN_OPTIONS=detect_leaks=$${ASAN_DETECT_LEAKS:-0} ./$(SANITIZER_RELATION_TEST_TARGET) ASAN_OPTIONS=detect_leaks=$${ASAN_DETECT_LEAKS:-0} ./$(SANITIZER_ANALYSIS_TEST_TARGET) + ASAN_OPTIONS=detect_leaks=$${ASAN_DETECT_LEAKS:-0} ./$(SANITIZER_EXHAUSTIVE_TEST_TARGET) + ASAN_OPTIONS=detect_leaks=$${ASAN_DETECT_LEAKS:-0} ./$(SANITIZER_IO_REPORT_TEST_TARGET) ASAN_OPTIONS=detect_leaks=$${ASAN_DETECT_LEAKS:-0} ./tests/run_tests.sh ./$(SANITIZER_TARGET) clean: rm -f $(TARGET) $(SANITIZER_TARGET) $(RELATION_TEST_TARGET) \ $(SANITIZER_RELATION_TEST_TARGET) $(ANALYSIS_TEST_TARGET) \ - $(SANITIZER_ANALYSIS_TEST_TARGET) + $(SANITIZER_ANALYSIS_TEST_TARGET) $(EXHAUSTIVE_TEST_TARGET) \ + $(SANITIZER_EXHAUSTIVE_TEST_TARGET) $(IO_REPORT_TEST_TARGET) \ + $(SANITIZER_IO_REPORT_TEST_TARGET) diff --git a/input.txt b/examples/sample.txt similarity index 100% rename from input.txt rename to examples/sample.txt diff --git a/include/application.h b/include/application.h index 0ccf664..c52812e 100644 --- a/include/application.h +++ b/include/application.h @@ -1,6 +1,7 @@ #ifndef BINARY_RELATION_ANALYZER_APPLICATION_H #define BINARY_RELATION_ANALYZER_APPLICATION_H -int run_application(void); +/** Runs the command-line application and returns a process exit code. */ +int run_application(int argc, char *argv[]); #endif diff --git a/include/relation.h b/include/relation.h index a5ba115..0ae7165 100644 --- a/include/relation.h +++ b/include/relation.h @@ -6,16 +6,39 @@ typedef struct Relation Relation; +/** + * Creates a relation over a copy of the supplied unique elements. + * + * Passing NULL is valid only when element_count is zero. Returns NULL when the + * arguments are invalid or storage cannot be allocated. + */ Relation *relation_create(const int *elements, size_t element_count); + +/** Creates a deep copy whose elements and pairs can be changed independently. */ Relation *relation_clone(const Relation *relation); + +/** Releases a relation and accepts NULL as a no-op. */ void relation_destroy(Relation *relation); +/** Returns the number of elements, or zero when relation is NULL. */ size_t relation_size(const Relation *relation); + +/** Returns the number of distinct ordered pairs, or zero for NULL. */ size_t relation_pair_count(const Relation *relation); + +/** Copies the element at index into element and returns whether the index is valid. */ bool relation_element_at(const Relation *relation, size_t index, int *element); + +/** Looks up an element and writes its matrix index when found. */ bool relation_index_of(const Relation *relation, int element, size_t *index); + +/** Tests membership of an index-based ordered pair. Invalid indices return false. */ bool relation_contains(const Relation *relation, size_t source_index, size_t target_index); + +/** Adds a pair identified by element values. Duplicate pairs are idempotent. */ bool relation_add_pair(Relation *relation, int source, int target); + +/** Adds a pair identified by matrix indices. Duplicate pairs are idempotent. */ bool relation_add_pair_at(Relation *relation, size_t source_index, size_t target_index); #endif diff --git a/include/relation_analysis.h b/include/relation_analysis.h index dca7e66..765cccc 100644 --- a/include/relation_analysis.h +++ b/include/relation_analysis.h @@ -16,6 +16,12 @@ typedef struct { bool partial_order; } RelationAnalysis; +/** + * Computes every supported property without modifying the relation. + * + * Properties over an empty set follow vacuous-truth semantics. Passing NULL + * returns an all-false result to make invalid use distinguishable from empty. + */ RelationAnalysis relation_analyze(const Relation *relation); #endif diff --git a/include/relation_closure.h b/include/relation_closure.h index 8bdf59c..a69e982 100644 --- a/include/relation_closure.h +++ b/include/relation_closure.h @@ -3,8 +3,13 @@ #include "relation.h" +/** Returns a new minimal reflexive superset of relation. */ Relation *relation_reflexive_closure(const Relation *relation); + +/** Returns a new minimal symmetric superset of relation. */ Relation *relation_symmetric_closure(const Relation *relation); + +/** Returns a new minimal transitive superset of relation. */ Relation *relation_transitive_closure(const Relation *relation); #endif diff --git a/include/relation_io.h b/include/relation_io.h index 6bec261..f654004 100644 --- a/include/relation_io.h +++ b/include/relation_io.h @@ -3,6 +3,10 @@ #include "relation.h" +/** + * Loads a relation from the documented whitespace-separated text format. + * Returns NULL and writes a diagnostic to stderr when input is invalid. + */ Relation *relation_load_from_file(const char *filename); #endif diff --git a/include/relation_report.h b/include/relation_report.h index 6b52bd1..12075d5 100644 --- a/include/relation_report.h +++ b/include/relation_report.h @@ -6,6 +6,7 @@ #include "relation.h" +/** Writes the matrix, analysis and closures without modifying relation. */ bool relation_write_report(const Relation *relation, FILE *output); #endif diff --git a/src/application.c b/src/application.c index 76b9de3..99fb7d7 100644 --- a/src/application.c +++ b/src/application.c @@ -1,18 +1,56 @@ #include #include #include +#include #include "application.h" #include "relation.h" #include "relation_io.h" #include "relation_report.h" -#define INPUT_FILE "input.txt" +#define PROGRAM_NAME "binary-relation-analyzer" -int run_application(void) { - Relation *relation = relation_load_from_file(INPUT_FILE); +static void write_usage(FILE *output) { + fprintf(output, "Usage: %s [--] INPUT_FILE\n", PROGRAM_NAME); + fprintf(output, " %s --help\n", PROGRAM_NAME); + fputs("Analyze the properties and closures of a finite binary relation.\n", output); +} + +static const char *parse_input_filename(int argc, char *argv[]) { + if (argc == 2 && argv[1][0] != '-') { + return argv[1]; + } + if (argc == 3 && strcmp(argv[1], "--") == 0) { + /* "--" permits filenames that begin with a dash. */ + return argv[2]; + } + return NULL; +} + +int run_application(int argc, char *argv[]) { + const char *input_filename; + Relation *relation; bool success; + if (argc == 2 && (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-h") == 0)) { + write_usage(stdout); + return EXIT_SUCCESS; + } + + input_filename = parse_input_filename(argc, argv); + if (input_filename == NULL) { + if (argc == 2 && strcmp(argv[1], "--") == 0) { + fputs("Error: expected an input file after --\n", stderr); + } else if (argc == 2 && argv[1][0] == '-') { + fprintf(stderr, "Error: unknown option: %s\n", argv[1]); + } else { + fputs("Error: expected exactly one input file\n", stderr); + } + write_usage(stderr); + return EXIT_FAILURE; + } + + relation = relation_load_from_file(input_filename); if (relation == NULL) { return EXIT_FAILURE; } diff --git a/src/main.c b/src/main.c index e354edc..fb9d86c 100644 --- a/src/main.c +++ b/src/main.c @@ -1,5 +1,5 @@ #include "application.h" -int main(void) { - return run_application(); +int main(int argc, char *argv[]) { + return run_application(argc, argv); } diff --git a/src/relation.c b/src/relation.c index e0fd079..23647f4 100644 --- a/src/relation.c +++ b/src/relation.c @@ -6,6 +6,7 @@ struct Relation { int *elements; + /* Ordered-pair membership is stored as a row-major square matrix. */ bool *pairs; size_t element_count; size_t pair_count; @@ -14,6 +15,7 @@ struct Relation { static bool allocation_sizes_are_valid(size_t element_count) { size_t pair_capacity; + /* Validate both multiplications before allocating attacker-controlled sizes. */ if (element_count > SIZE_MAX / sizeof(int)) { return false; } @@ -56,6 +58,7 @@ Relation *relation_create(const int *elements, size_t element_count) { } if (element_count > 0) { + /* Relation owns its labels so callers may release their input array. */ relation->elements = malloc(element_count * sizeof(*relation->elements)); relation->pairs = calloc(element_count * element_count, sizeof(*relation->pairs)); if (relation->elements == NULL || relation->pairs == NULL) { diff --git a/src/relation_analysis.c b/src/relation_analysis.c index 7284298..ea62934 100644 --- a/src/relation_analysis.c +++ b/src/relation_analysis.c @@ -35,6 +35,7 @@ RelationAnalysis relation_analyze(const Relation *relation) { return analysis; } + /* Every universal property starts true, including for the empty set. */ analysis.reflexive = true; analysis.irreflexive = true; analysis.symmetric = true; diff --git a/src/relation_closure.c b/src/relation_closure.c index 814f188..c15e763 100644 --- a/src/relation_closure.c +++ b/src/relation_closure.c @@ -47,6 +47,7 @@ Relation *relation_transitive_closure(const Relation *relation) { return NULL; } + /* Warshall's algorithm progressively admits each intermediate element. */ for (intermediate = 0; intermediate < size; intermediate++) { for (source = 0; source < size; source++) { if (!relation_contains(closure, source, intermediate)) { diff --git a/src/relation_io.c b/src/relation_io.c index 4d8700c..8f527bc 100644 --- a/src/relation_io.c +++ b/src/relation_io.c @@ -9,6 +9,7 @@ #include "relation_io.h" #define INPUT_BUFFER_SIZE 4096 +/* The file format is deliberately bounded even though the model is not. */ #define MAX_INPUT_ELEMENTS 50 static bool parse_next_integer(const char **cursor, int *value) { diff --git a/src/relation_report.c b/src/relation_report.c index 5bec659..82f24f1 100644 --- a/src/relation_report.c +++ b/src/relation_report.c @@ -132,6 +132,7 @@ static void write_closure(const char *name, const Relation *relation, const Rela size_t target; fprintf(output, "%s closure = {", name); + /* Preserve input pairs first, then append only pairs introduced by the closure. */ for (source = 0; source < relation_size(relation); source++) { for (target = 0; target < relation_size(relation); target++) { if (relation_contains(relation, source, target)) { diff --git a/tests/fixtures/blank_header/input.txt b/tests/fixtures/blank_header/input.txt new file mode 100644 index 0000000..bac9c71 --- /dev/null +++ b/tests/fixtures/blank_header/input.txt @@ -0,0 +1 @@ + diff --git a/tests/fixtures/extra_header_element/input.txt b/tests/fixtures/extra_header_element/input.txt new file mode 100644 index 0000000..8caa16d --- /dev/null +++ b/tests/fixtures/extra_header_element/input.txt @@ -0,0 +1 @@ +1 10 20 diff --git a/tests/fixtures/incomplete_pair/input.txt b/tests/fixtures/incomplete_pair/input.txt new file mode 100644 index 0000000..7eb46ef --- /dev/null +++ b/tests/fixtures/incomplete_pair/input.txt @@ -0,0 +1,2 @@ +2 1 2 +1 diff --git a/tests/fixtures/integer_boundaries/input.txt b/tests/fixtures/integer_boundaries/input.txt new file mode 100644 index 0000000..8a4fe2e --- /dev/null +++ b/tests/fixtures/integer_boundaries/input.txt @@ -0,0 +1,2 @@ +2 -2147483648 2147483647 +-2147483648 2147483647 diff --git a/tests/fixtures/invalid_count_token/input.txt b/tests/fixtures/invalid_count_token/input.txt new file mode 100644 index 0000000..dc375c0 --- /dev/null +++ b/tests/fixtures/invalid_count_token/input.txt @@ -0,0 +1 @@ +three 1 2 3 diff --git a/tests/fixtures/invalid_pair_token/input.txt b/tests/fixtures/invalid_pair_token/input.txt new file mode 100644 index 0000000..014c733 --- /dev/null +++ b/tests/fixtures/invalid_pair_token/input.txt @@ -0,0 +1,2 @@ +2 1 2 +1 two diff --git a/tests/fixtures/maximum_element_count/input.txt b/tests/fixtures/maximum_element_count/input.txt new file mode 100644 index 0000000..a2f5d12 --- /dev/null +++ b/tests/fixtures/maximum_element_count/input.txt @@ -0,0 +1 @@ +50 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 diff --git a/tests/fixtures/negative_count/input.txt b/tests/fixtures/negative_count/input.txt new file mode 100644 index 0000000..3a2e3f4 --- /dev/null +++ b/tests/fixtures/negative_count/input.txt @@ -0,0 +1 @@ +-1 diff --git a/tests/fixtures/nonadjacent_duplicate_element/input.txt b/tests/fixtures/nonadjacent_duplicate_element/input.txt new file mode 100644 index 0000000..abea2d4 --- /dev/null +++ b/tests/fixtures/nonadjacent_duplicate_element/input.txt @@ -0,0 +1 @@ +3 10 20 10 diff --git a/tests/fixtures/overflowing_element/input.txt b/tests/fixtures/overflowing_element/input.txt new file mode 100644 index 0000000..32ecb03 --- /dev/null +++ b/tests/fixtures/overflowing_element/input.txt @@ -0,0 +1 @@ +1 999999999999999999999999999999 diff --git a/tests/fixtures/overflowing_pair/input.txt b/tests/fixtures/overflowing_pair/input.txt new file mode 100644 index 0000000..d2e7e8e --- /dev/null +++ b/tests/fixtures/overflowing_pair/input.txt @@ -0,0 +1,2 @@ +2 1 2 +1 999999999999999999999999999999 diff --git a/tests/fixtures/unknown_source/input.txt b/tests/fixtures/unknown_source/input.txt new file mode 100644 index 0000000..246bf67 --- /dev/null +++ b/tests/fixtures/unknown_source/input.txt @@ -0,0 +1,2 @@ +2 1 2 +3 1 diff --git a/tests/fixtures/whitespace_and_blank_lines/input.txt b/tests/fixtures/whitespace_and_blank_lines/input.txt new file mode 100644 index 0000000..5703ee5 --- /dev/null +++ b/tests/fixtures/whitespace_and_blank_lines/input.txt @@ -0,0 +1,4 @@ +2 +10 -20 + + 10 -20 + diff --git a/tests/run_tests.sh b/tests/run_tests.sh index 9ed579a..797f801 100755 --- a/tests/run_tests.sh +++ b/tests/run_tests.sh @@ -4,7 +4,7 @@ set -u script_dir="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" fixtures_dir="${script_dir}/fixtures" -binary_argument="${1:-./a.out}" +binary_argument="${1:-./binary-relation-analyzer}" if [[ "${binary_argument}" = /* ]]; then binary="${binary_argument}" @@ -12,6 +12,9 @@ else binary="$(cd -- "$(dirname -- "${binary_argument}")" && pwd)/$(basename -- "${binary_argument}")" fi +temporary_dir="$(mktemp -d)" +trap 'rm -rf -- "${temporary_dir}"' EXIT + passed=0 failed=0 @@ -22,7 +25,7 @@ run_success() { local expected local case_failed=0 - if ! output="$(cd -- "${fixtures_dir}/${fixture}" && "${binary}" 2>&1)"; then + if ! output="$(cd -- "${fixtures_dir}/${fixture}" && "${binary}" input.txt 2>&1)"; then printf 'FAIL %s: expected a successful exit\n%s\n' "${fixture}" "${output}" failed=$((failed + 1)) return @@ -57,7 +60,7 @@ run_failure() { local expected="$2" local output - if output="$(cd -- "${fixtures_dir}/${fixture}" && "${binary}" 2>&1)"; then + if output="$(cd -- "${fixtures_dir}/${fixture}" && "${binary}" input.txt 2>&1)"; then printf 'FAIL %s: expected a non-zero exit\n%s\n' "${fixture}" "${output}" failed=$((failed + 1)) return @@ -81,6 +84,62 @@ run_failure() { passed=$((passed + 1)) } +run_cli_success() { + local label="$1" + local expected="$2" + shift 2 + local output + + if ! output="$("${binary}" "$@" 2>&1)"; then + printf 'FAIL %s: expected a successful exit\n%s\n' "${label}" "${output}" + failed=$((failed + 1)) + return + fi + if [[ "${output}" == *'ERROR: AddressSanitizer'* || + "${output}" == *'runtime error:'* || + "${output}" == *'LeakSanitizer'* ]]; then + printf 'FAIL %s: sanitizer diagnostic detected\n%s\n' "${label}" "${output}" + failed=$((failed + 1)) + return + fi + if [[ "${output}" != *"${expected}"* ]]; then + printf 'FAIL %s: missing output: %s\n%s\n' "${label}" "${expected}" "${output}" + failed=$((failed + 1)) + return + fi + + printf 'PASS %s\n' "${label}" + passed=$((passed + 1)) +} + +run_cli_failure() { + local label="$1" + local expected="$2" + shift 2 + local output + + if output="$("${binary}" "$@" 2>&1)"; then + printf 'FAIL %s: expected a non-zero exit\n%s\n' "${label}" "${output}" + failed=$((failed + 1)) + return + fi + if [[ "${output}" == *'ERROR: AddressSanitizer'* || + "${output}" == *'runtime error:'* || + "${output}" == *'LeakSanitizer'* ]]; then + printf 'FAIL %s: sanitizer diagnostic detected\n%s\n' "${label}" "${output}" + failed=$((failed + 1)) + return + fi + if [[ "${output}" != *"${expected}"* ]]; then + printf 'FAIL %s: missing error: %s\n%s\n' "${label}" "${expected}" "${output}" + failed=$((failed + 1)) + return + fi + + printf 'PASS %s\n' "${label}" + passed=$((passed + 1)) +} + run_success legacy_sample \ '1. Reflexive: true' \ '3. Symmetric: true' \ @@ -160,6 +219,64 @@ run_failure unknown_element 'unknown element' run_failure malformed_pair 'invalid ordered pair' run_failure invalid_count 'invalid element count' run_failure missing_file 'could not open input file input.txt' +run_failure negative_count 'invalid element count' +run_failure invalid_count_token 'invalid element count' +run_failure overflowing_element 'wrong number of elements' +run_failure extra_header_element 'wrong number of elements' +run_failure nonadjacent_duplicate_element 'duplicate elements' +run_failure incomplete_pair 'invalid ordered pair' +run_failure invalid_pair_token 'invalid ordered pair' +run_failure overflowing_pair 'invalid ordered pair' +run_failure unknown_source 'unknown element' +run_failure blank_header 'invalid element count' + +run_success whitespace_and_blank_lines \ + '1. Reflexive: false' \ + 'Symmetric closure = {(10,-20),(-20,10)}' + +run_success integer_boundaries \ + 'Missing symmetric pairs: (2147483647,-2147483648);' \ + 'Symmetric closure = {(-2147483648,2147483647),(2147483647,-2147483648)}' + +run_success maximum_element_count \ + 'Relation properties:' \ + '1. Reflexive: false' \ + 'Partial order: false' + +run_cli_success help_long 'Usage: binary-relation-analyzer [--] INPUT_FILE' --help +run_cli_success help_short 'Analyze the properties and closures' -h +run_cli_success explicit_path 'Equivalence relation: true' \ + "${fixtures_dir}/legacy_sample/input.txt" +run_cli_success option_separator 'Relation properties:' -- \ + "${fixtures_dir}/empty_set/input.txt" +run_cli_failure no_arguments 'expected exactly one input file' +run_cli_failure too_many_arguments 'expected exactly one input file' \ + "${fixtures_dir}/empty_set/input.txt" extra +run_cli_failure unknown_option 'unknown option: --version' --version +run_cli_failure missing_after_separator 'expected an input file after --' -- +run_cli_failure help_with_argument 'expected exactly one input file' --help extra +run_cli_failure empty_path 'could not open input file' '' + +: >"${temporary_dir}/empty.txt" +run_cli_failure empty_file 'missing or overlong header' "${temporary_dir}/empty.txt" + +{ + for ((character = 0; character < 5000; character++)); do + printf '0' + done + printf '\n' +} >"${temporary_dir}/overlong-header.txt" +run_cli_failure overlong_header 'missing or overlong header' \ + "${temporary_dir}/overlong-header.txt" + +{ + printf '1 1\n' + for ((character = 0; character < 5000; character++)); do + printf ' ' + done + printf '\n' +} >"${temporary_dir}/overlong-pair.txt" +run_cli_failure overlong_pair 'line is too long' "${temporary_dir}/overlong-pair.txt" printf '\n%d passed, %d failed\n' "${passed}" "${failed}" ((failed == 0)) diff --git a/tests/test_analysis.c b/tests/test_analysis.c index aa42c85..2636e01 100644 --- a/tests/test_analysis.c +++ b/tests/test_analysis.c @@ -17,6 +17,22 @@ static Relation *create_relation(const int *elements, size_t element_count, return relation; } +static void test_null_inputs(void) { + RelationAnalysis analysis = relation_analyze(NULL); + + assert(!analysis.reflexive); + assert(!analysis.irreflexive); + assert(!analysis.symmetric); + assert(!analysis.antisymmetric); + assert(!analysis.asymmetric); + assert(!analysis.transitive); + assert(!analysis.equivalence); + assert(!analysis.partial_order); + assert(relation_reflexive_closure(NULL) == NULL); + assert(relation_symmetric_closure(NULL) == NULL); + assert(relation_transitive_closure(NULL) == NULL); +} + static void test_empty_set_properties(void) { Relation *relation = relation_create(NULL, 0); RelationAnalysis analysis; @@ -34,6 +50,36 @@ static void test_empty_set_properties(void) { relation_destroy(relation); } +static void test_singleton_properties(void) { + const int elements[] = {7}; + Relation *without_loop = create_relation(elements, 1, NULL, 0); + Relation *with_loop = create_relation(elements, 1, NULL, 0); + RelationAnalysis analysis = relation_analyze(without_loop); + + assert(!analysis.reflexive); + assert(analysis.irreflexive); + assert(analysis.symmetric); + assert(analysis.antisymmetric); + assert(analysis.asymmetric); + assert(analysis.transitive); + assert(!analysis.equivalence); + assert(!analysis.partial_order); + + assert(relation_add_pair(with_loop, 7, 7)); + analysis = relation_analyze(with_loop); + assert(analysis.reflexive); + assert(!analysis.irreflexive); + assert(analysis.symmetric); + assert(analysis.antisymmetric); + assert(!analysis.asymmetric); + assert(analysis.transitive); + assert(analysis.equivalence); + assert(analysis.partial_order); + + relation_destroy(without_loop); + relation_destroy(with_loop); +} + static void test_equivalence_properties(void) { const int elements[] = {10, 20}; const int pairs[][2] = {{10, 10}, {10, 20}, {20, 10}, {20, 20}}; @@ -123,7 +169,9 @@ static void test_closures_are_independent_and_minimal(void) { } int main(void) { + test_null_inputs(); test_empty_set_properties(); + test_singleton_properties(); test_equivalence_properties(); test_partial_order_properties(); test_asymmetric_and_non_transitive_properties(); diff --git a/tests/test_exhaustive.c b/tests/test_exhaustive.c new file mode 100644 index 0000000..7150146 --- /dev/null +++ b/tests/test_exhaustive.c @@ -0,0 +1,222 @@ +#include +#include +#include + +#include "relation.h" +#include "relation_analysis.h" +#include "relation_closure.h" + +#define MAX_EXHAUSTIVE_ELEMENTS 3 + +typedef bool ReferenceMatrix[MAX_EXHAUSTIVE_ELEMENTS][MAX_EXHAUSTIVE_ELEMENTS]; + +static void matrix_from_mask(ReferenceMatrix matrix, size_t size, unsigned int mask) { + size_t source; + size_t target; + + for (source = 0; source < size; source++) { + for (target = 0; target < size; target++) { + size_t bit = source * size + target; + matrix[source][target] = (mask & (1U << bit)) != 0; + } + } +} + +static void copy_matrix(ReferenceMatrix destination, ReferenceMatrix source, size_t size) { + size_t row; + size_t column; + + for (row = 0; row < size; row++) { + for (column = 0; column < size; column++) { + destination[row][column] = source[row][column]; + } + } +} + +static Relation *relation_from_matrix(ReferenceMatrix matrix, size_t size) { + const int elements[] = {10, 20, 30}; + Relation *relation = relation_create(elements, size); + size_t source; + size_t target; + + assert(relation != NULL); + for (source = 0; source < size; source++) { + for (target = 0; target < size; target++) { + if (matrix[source][target]) { + assert(relation_add_pair_at(relation, source, target)); + } + } + } + return relation; +} + +static RelationAnalysis reference_analysis(ReferenceMatrix matrix, size_t size) { + RelationAnalysis analysis = {true, true, true, true, true, true, false, false}; + size_t source; + size_t intermediate; + size_t target; + + for (source = 0; source < size; source++) { + if (matrix[source][source]) { + analysis.irreflexive = false; + } else { + analysis.reflexive = false; + } + + for (target = 0; target < size; target++) { + if (matrix[source][target] != matrix[target][source]) { + analysis.symmetric = false; + } + if (source != target && matrix[source][target] && matrix[target][source]) { + analysis.antisymmetric = false; + } + if (matrix[source][target] && matrix[target][source]) { + analysis.asymmetric = false; + } + } + } + + for (source = 0; source < size; source++) { + for (intermediate = 0; intermediate < size; intermediate++) { + for (target = 0; target < size; target++) { + if (matrix[source][intermediate] && matrix[intermediate][target] && + !matrix[source][target]) { + analysis.transitive = false; + } + } + } + } + + analysis.equivalence = analysis.reflexive && analysis.symmetric && analysis.transitive; + analysis.partial_order = analysis.reflexive && analysis.antisymmetric && analysis.transitive; + return analysis; +} + +static void compute_reference_closures(ReferenceMatrix original, size_t size, + ReferenceMatrix reflexive, ReferenceMatrix symmetric, + ReferenceMatrix transitive) { + bool changed; + size_t source; + size_t intermediate; + size_t target; + + copy_matrix(reflexive, original, size); + copy_matrix(symmetric, original, size); + copy_matrix(transitive, original, size); + + for (source = 0; source < size; source++) { + reflexive[source][source] = true; + for (target = 0; target < size; target++) { + if (original[source][target]) { + symmetric[target][source] = true; + } + } + } + + /* Saturation is intentionally independent from the production Warshall loop. */ + do { + changed = false; + for (source = 0; source < size; source++) { + for (intermediate = 0; intermediate < size; intermediate++) { + for (target = 0; target < size; target++) { + if (transitive[source][intermediate] && transitive[intermediate][target] && + !transitive[source][target]) { + transitive[source][target] = true; + changed = true; + } + } + } + } + } while (changed); +} + +static size_t matrix_pair_count(ReferenceMatrix matrix, size_t size) { + size_t count = 0; + size_t source; + size_t target; + + for (source = 0; source < size; source++) { + for (target = 0; target < size; target++) { + if (matrix[source][target]) { + count++; + } + } + } + return count; +} + +static void assert_analysis_equal(RelationAnalysis actual, RelationAnalysis expected) { + assert(actual.reflexive == expected.reflexive); + assert(actual.irreflexive == expected.irreflexive); + assert(actual.symmetric == expected.symmetric); + assert(actual.antisymmetric == expected.antisymmetric); + assert(actual.asymmetric == expected.asymmetric); + assert(actual.transitive == expected.transitive); + assert(actual.equivalence == expected.equivalence); + assert(actual.partial_order == expected.partial_order); +} + +static void assert_relation_equals_matrix(const Relation *relation, + ReferenceMatrix expected, size_t size) { + size_t source; + size_t target; + + assert(relation_size(relation) == size); + assert(relation_pair_count(relation) == matrix_pair_count(expected, size)); + for (source = 0; source < size; source++) { + for (target = 0; target < size; target++) { + assert(relation_contains(relation, source, target) == expected[source][target]); + } + } +} + +static void verify_relation(size_t size, unsigned int mask) { + ReferenceMatrix original = {{false}}; + ReferenceMatrix reflexive = {{false}}; + ReferenceMatrix symmetric = {{false}}; + ReferenceMatrix transitive = {{false}}; + Relation *relation; + Relation *reflexive_closure; + Relation *symmetric_closure; + Relation *transitive_closure; + + matrix_from_mask(original, size, mask); + relation = relation_from_matrix(original, size); + assert_analysis_equal(relation_analyze(relation), reference_analysis(original, size)); + + compute_reference_closures(original, size, reflexive, symmetric, transitive); + reflexive_closure = relation_reflexive_closure(relation); + symmetric_closure = relation_symmetric_closure(relation); + transitive_closure = relation_transitive_closure(relation); + assert(reflexive_closure != NULL); + assert(symmetric_closure != NULL); + assert(transitive_closure != NULL); + + assert_relation_equals_matrix(reflexive_closure, reflexive, size); + assert_relation_equals_matrix(symmetric_closure, symmetric, size); + assert_relation_equals_matrix(transitive_closure, transitive, size); + assert_relation_equals_matrix(relation, original, size); + assert(relation_analyze(reflexive_closure).reflexive); + assert(relation_analyze(symmetric_closure).symmetric); + assert(relation_analyze(transitive_closure).transitive); + + relation_destroy(reflexive_closure); + relation_destroy(symmetric_closure); + relation_destroy(transitive_closure); + relation_destroy(relation); +} + +int main(void) { + size_t size; + + /* Enumerate every binary relation over sets containing up to three elements. */ + for (size = 0; size <= MAX_EXHAUSTIVE_ELEMENTS; size++) { + unsigned int relation_count = 1U << (size * size); + unsigned int mask; + + for (mask = 0; mask < relation_count; mask++) { + verify_relation(size, mask); + } + } + return 0; +} diff --git a/tests/test_io_report.c b/tests/test_io_report.c new file mode 100644 index 0000000..89d0f35 --- /dev/null +++ b/tests/test_io_report.c @@ -0,0 +1,59 @@ +#include +#include +#include + +#include "relation.h" +#include "relation_io.h" +#include "relation_report.h" + +static void test_load_valid_relation(void) { + Relation *relation = + relation_load_from_file("tests/fixtures/equivalence_multi_digit/input.txt"); + int first = 0; + int second = 0; + + assert(relation != NULL); + assert(relation_size(relation) == 2); + assert(relation_pair_count(relation) == 4); + assert(relation_element_at(relation, 0, &first)); + assert(relation_element_at(relation, 1, &second)); + assert(first == 10); + assert(second == -20); + relation_destroy(relation); +} + +static void test_report_content_and_immutability(void) { + const int elements[] = {1, 2}; + Relation *relation = relation_create(elements, 2); + FILE *output = tmpfile(); + char report[4096]; + size_t bytes_read; + + assert(relation != NULL); + assert(output != NULL); + assert(relation_add_pair(relation, 1, 2)); + assert(relation_write_report(relation, output)); + assert(relation_pair_count(relation) == 1); + assert(relation_contains(relation, 0, 1)); + assert(!relation_contains(relation, 1, 0)); + + rewind(output); + bytes_read = fread(report, 1, sizeof(report) - 1, output); + assert(!ferror(output)); + report[bytes_read] = '\0'; + assert(strstr(report, "Relation properties:") != NULL); + assert(strstr(report, "3. Symmetric: false") != NULL); + assert(strstr(report, "Missing symmetric pairs: (2,1);") != NULL); + assert(strstr(report, "Symmetric closure = {(1,2),(2,1)}") != NULL); + + assert(!relation_write_report(NULL, output)); + assert(!relation_write_report(relation, NULL)); + fclose(output); + relation_destroy(relation); +} + +int main(void) { + test_load_valid_relation(); + test_report_content_and_immutability(); + return 0; +} diff --git a/tests/test_relation.c b/tests/test_relation.c index 9b2551f..fd3e89e 100644 --- a/tests/test_relation.c +++ b/tests/test_relation.c @@ -5,24 +5,53 @@ #include "relation.h" +static void test_null_inputs(void) { + int element = 0; + size_t index = 0; + + assert(relation_size(NULL) == 0); + assert(relation_pair_count(NULL) == 0); + assert(relation_clone(NULL) == NULL); + assert(!relation_element_at(NULL, 0, &element)); + assert(!relation_index_of(NULL, 1, &index)); + assert(!relation_contains(NULL, 0, 0)); + assert(!relation_add_pair(NULL, 1, 1)); + assert(!relation_add_pair_at(NULL, 0, 0)); + relation_destroy(NULL); +} + static void test_empty_relation(void) { Relation *relation = relation_create(NULL, 0); + Relation *clone; + int element = 0; + size_t index = 0; assert(relation != NULL); assert(relation_size(relation) == 0); assert(relation_pair_count(relation) == 0); + assert(!relation_element_at(relation, 0, &element)); + assert(!relation_index_of(relation, 0, &index)); assert(!relation_contains(relation, 0, 0)); + assert(!relation_add_pair(relation, 0, 0)); + assert(!relation_add_pair_at(relation, 0, 0)); + + clone = relation_clone(relation); + assert(clone != NULL); + assert(relation_size(clone) == 0); + assert(relation_pair_count(clone) == 0); + relation_destroy(clone); relation_destroy(relation); } static void test_invalid_elements(void) { const int duplicates[] = {10, 10}; + const int nonadjacent_duplicates[] = {10, 20, 10}; const int element = 10; assert(relation_create(NULL, 1) == NULL); assert(relation_create(duplicates, 2) == NULL); + assert(relation_create(nonadjacent_duplicates, 3) == NULL); assert(relation_create(&element, SIZE_MAX) == NULL); - assert(relation_clone(NULL) == NULL); } static void test_dynamic_element_storage(void) { @@ -52,18 +81,33 @@ static void test_elements_and_pairs(void) { assert(relation_size(relation) == 3); assert(relation_element_at(relation, 1, &element)); assert(element == -20); + assert(relation_element_at(relation, 0, &element)); + assert(element == 10); + assert(relation_element_at(relation, 2, &element)); + assert(element == 30); assert(!relation_element_at(relation, 3, &element)); + assert(!relation_element_at(relation, 0, NULL)); + assert(relation_index_of(relation, 10, &index)); + assert(index == 0); assert(relation_index_of(relation, 30, &index)); assert(index == 2); assert(!relation_index_of(relation, 99, &index)); + assert(!relation_index_of(relation, 10, NULL)); assert(relation_add_pair(relation, 10, -20)); assert(relation_contains(relation, 0, 1)); assert(relation_pair_count(relation) == 1); assert(relation_add_pair(relation, 10, -20)); assert(relation_pair_count(relation) == 1); + assert(relation_add_pair(relation, 30, 30)); + assert(relation_contains(relation, 2, 2)); + assert(relation_pair_count(relation) == 2); + assert(!relation_add_pair(relation, 99, 10)); assert(!relation_add_pair(relation, 10, 99)); assert(!relation_add_pair_at(relation, 3, 0)); + assert(!relation_add_pair_at(relation, 0, 3)); + assert(!relation_contains(relation, 3, 0)); + assert(!relation_contains(relation, 0, 3)); relation_destroy(relation); } @@ -90,11 +134,11 @@ static void test_clone_is_independent(void) { } int main(void) { + test_null_inputs(); test_empty_relation(); test_invalid_elements(); test_dynamic_element_storage(); test_elements_and_pairs(); test_clone_is_independent(); - relation_destroy(NULL); return 0; } From 8c9f6ab25f3088983e2b1a74ffe5af242df2e1b2 Mon Sep 17 00:00:00 2001 From: Lucas Mariz Date: Sat, 12 Sep 2026 12:00:02 -0300 Subject: [PATCH 5/6] build: standardize quality checks and artifacts --- .clang-format | 13 ++ .editorconfig | 23 ++++ .github/workflows/ci.yml | 26 +++- .gitignore | 10 +- Makefile | 276 +++++++++++++++++++++++++++++++-------- src/relation_report.c | 6 +- tests/test_analysis.c | 4 +- tests/test_exhaustive.c | 4 +- 8 files changed, 286 insertions(+), 76 deletions(-) create mode 100644 .clang-format create mode 100644 .editorconfig diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..cc3eb6d --- /dev/null +++ b/.clang-format @@ -0,0 +1,13 @@ +--- +BasedOnStyle: LLVM +Language: Cpp +ColumnLimit: 100 +IndentWidth: 4 +ContinuationIndentWidth: 4 +UseTab: Never +BreakBeforeBraces: Attach +AllowShortFunctionsOnASingleLine: None +DerivePointerAlignment: false +PointerAlignment: Right +SortIncludes: CaseSensitive +... diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a9c68ae --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{c,h}] +indent_style = space +indent_size = 4 + +[Makefile] +indent_style = tab + +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +[*.md] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 123f462..07c167f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,7 +11,7 @@ permissions: jobs: build-and-test: name: Build and test (${{ matrix.compiler }}) - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 10 strategy: fail-fast: false @@ -32,7 +32,7 @@ jobs: sanitizers: name: Address and undefined behavior sanitizers - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 10 env: CC: gcc @@ -45,3 +45,25 @@ jobs: - name: Build and run sanitized tests run: make sanitize + + quality: + name: Formatting, static analysis and coverage + runs-on: ubuntu-24.04 + timeout-minutes: 10 + env: + CC: gcc + CLANG_FORMAT: clang-format-18 + steps: + - name: Check out repository + uses: actions/checkout@v7 + with: + persist-credentials: false + + - name: Check source formatting + run: make check-format + + - name: Run GCC static analyzer + run: make analyze + + - name: Run tests with coverage instrumentation + run: make coverage diff --git a/.gitignore b/.gitignore index 63716a0..a1168ef 100644 --- a/.gitignore +++ b/.gitignore @@ -1,13 +1,5 @@ /binary-relation-analyzer -/binary-relation-analyzer-sanitize -/relation-tests -/relation-tests-sanitize -/analysis-tests -/analysis-tests-sanitize -/exhaustive-tests -/exhaustive-tests-sanitize -/io-report-tests -/io-report-tests-sanitize +/build/ *.o *.gcda *.gcno diff --git a/Makefile b/Makefile index 43f7341..49d2e7b 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,8 @@ CC ?= cc +COVERAGE_CC ?= gcc +ANALYZER_CC ?= gcc +GCOV ?= gcov +CLANG_FORMAT ?= clang-format CPPFLAGS ?= CFLAGS ?= -O2 LDFLAGS ?= @@ -6,80 +10,238 @@ LDLIBS ?= STANDARD_FLAGS := -std=c17 WARNING_FLAGS := -Wall -Wextra -Wpedantic -Wconversion -Wshadow -Werror +DEPENDENCY_FLAGS := -MMD -MP SANITIZER_FLAGS := -g -O1 -fno-omit-frame-pointer -fsanitize=address,undefined +COVERAGE_FLAGS := -g -O0 --coverage +ANALYZER_FLAGS := -g -O0 -fanalyzer + +# Isolated object trees prevent incompatible compiler flags from sharing artifacts. +BUILD_DIR := build +RELEASE_OBJECT_DIR := $(BUILD_DIR)/release/objects +SANITIZER_DIR := $(BUILD_DIR)/sanitize +SANITIZER_OBJECT_DIR := $(SANITIZER_DIR)/objects +SANITIZER_TEST_DIR := $(SANITIZER_DIR)/tests +COVERAGE_DIR := $(BUILD_DIR)/coverage +COVERAGE_OBJECT_DIR := $(COVERAGE_DIR)/objects +COVERAGE_TEST_DIR := $(COVERAGE_DIR)/tests +ANALYZER_OBJECT_DIR := $(BUILD_DIR)/analyzer/objects +TEST_DIR := $(BUILD_DIR)/tests TARGET := binary-relation-analyzer -SANITIZER_TARGET := binary-relation-analyzer-sanitize -RELATION_TEST_TARGET := relation-tests -SANITIZER_RELATION_TEST_TARGET := relation-tests-sanitize -ANALYSIS_TEST_TARGET := analysis-tests -SANITIZER_ANALYSIS_TEST_TARGET := analysis-tests-sanitize -EXHAUSTIVE_TEST_TARGET := exhaustive-tests -SANITIZER_EXHAUSTIVE_TEST_TARGET := exhaustive-tests-sanitize -IO_REPORT_TEST_TARGET := io-report-tests -SANITIZER_IO_REPORT_TEST_TARGET := io-report-tests-sanitize -APP_SOURCES := src/main.c src/application.c src/relation.c src/relation_analysis.c \ - src/relation_closure.c src/relation_io.c src/relation_report.c -RELATION_SOURCE := src/relation.c -ANALYSIS_SOURCES := src/relation.c src/relation_analysis.c src/relation_closure.c -IO_REPORT_SOURCES := $(ANALYSIS_SOURCES) src/relation_io.c src/relation_report.c -HEADERS := $(wildcard include/*.h) +SANITIZER_TARGET := $(SANITIZER_DIR)/binary-relation-analyzer +COVERAGE_TARGET := $(COVERAGE_DIR)/binary-relation-analyzer + +RELATION_TEST_TARGET := $(TEST_DIR)/relation-tests +ANALYSIS_TEST_TARGET := $(TEST_DIR)/analysis-tests +EXHAUSTIVE_TEST_TARGET := $(TEST_DIR)/exhaustive-tests +IO_REPORT_TEST_TARGET := $(TEST_DIR)/io-report-tests +TEST_TARGETS := $(RELATION_TEST_TARGET) $(ANALYSIS_TEST_TARGET) \ + $(EXHAUSTIVE_TEST_TARGET) $(IO_REPORT_TEST_TARGET) + +SANITIZER_RELATION_TEST_TARGET := $(SANITIZER_TEST_DIR)/relation-tests +SANITIZER_ANALYSIS_TEST_TARGET := $(SANITIZER_TEST_DIR)/analysis-tests +SANITIZER_EXHAUSTIVE_TEST_TARGET := $(SANITIZER_TEST_DIR)/exhaustive-tests +SANITIZER_IO_REPORT_TEST_TARGET := $(SANITIZER_TEST_DIR)/io-report-tests +SANITIZER_TEST_TARGETS := $(SANITIZER_RELATION_TEST_TARGET) \ + $(SANITIZER_ANALYSIS_TEST_TARGET) $(SANITIZER_EXHAUSTIVE_TEST_TARGET) \ + $(SANITIZER_IO_REPORT_TEST_TARGET) -.PHONY: all test sanitize clean +COVERAGE_RELATION_TEST_TARGET := $(COVERAGE_TEST_DIR)/relation-tests +COVERAGE_ANALYSIS_TEST_TARGET := $(COVERAGE_TEST_DIR)/analysis-tests +COVERAGE_EXHAUSTIVE_TEST_TARGET := $(COVERAGE_TEST_DIR)/exhaustive-tests +COVERAGE_IO_REPORT_TEST_TARGET := $(COVERAGE_TEST_DIR)/io-report-tests +COVERAGE_TEST_TARGETS := $(COVERAGE_RELATION_TEST_TARGET) $(COVERAGE_ANALYSIS_TEST_TARGET) \ + $(COVERAGE_EXHAUSTIVE_TEST_TARGET) $(COVERAGE_IO_REPORT_TEST_TARGET) + +APP_SOURCE_NAMES := main application relation relation_analysis relation_closure relation_io \ + relation_report +TEST_SOURCE_NAMES := test_relation test_analysis test_exhaustive test_io_report +APP_SOURCES := $(addprefix src/,$(addsuffix .c,$(APP_SOURCE_NAMES))) +TEST_SOURCES := $(addprefix tests/,$(addsuffix .c,$(TEST_SOURCE_NAMES))) +HEADERS := $(wildcard include/*.h) +FORMAT_SOURCES := $(sort $(APP_SOURCES) $(TEST_SOURCES) $(HEADERS)) + +APP_OBJECTS := $(addprefix $(RELEASE_OBJECT_DIR)/src/,$(addsuffix .o,$(APP_SOURCE_NAMES))) +RELATION_OBJECT := $(RELEASE_OBJECT_DIR)/src/relation.o +ANALYSIS_OBJECTS := $(addprefix $(RELEASE_OBJECT_DIR)/src/,$(addsuffix .o,relation \ + relation_analysis relation_closure)) +IO_REPORT_OBJECTS := $(addprefix $(RELEASE_OBJECT_DIR)/src/,$(addsuffix .o,relation \ + relation_analysis relation_closure relation_io relation_report)) + +SANITIZER_APP_OBJECTS := $(addprefix $(SANITIZER_OBJECT_DIR)/src/, \ + $(addsuffix .o,$(APP_SOURCE_NAMES))) +SANITIZER_RELATION_OBJECT := $(SANITIZER_OBJECT_DIR)/src/relation.o +SANITIZER_ANALYSIS_OBJECTS := $(addprefix $(SANITIZER_OBJECT_DIR)/src/, \ + $(addsuffix .o,relation relation_analysis relation_closure)) +SANITIZER_IO_REPORT_OBJECTS := $(addprefix $(SANITIZER_OBJECT_DIR)/src/, \ + $(addsuffix .o,relation relation_analysis relation_closure relation_io relation_report)) + +COVERAGE_APP_OBJECTS := $(addprefix $(COVERAGE_OBJECT_DIR)/src/, \ + $(addsuffix .o,$(APP_SOURCE_NAMES))) +COVERAGE_RELATION_OBJECT := $(COVERAGE_OBJECT_DIR)/src/relation.o +COVERAGE_ANALYSIS_OBJECTS := $(addprefix $(COVERAGE_OBJECT_DIR)/src/, \ + $(addsuffix .o,relation relation_analysis relation_closure)) +COVERAGE_IO_REPORT_OBJECTS := $(addprefix $(COVERAGE_OBJECT_DIR)/src/, \ + $(addsuffix .o,relation relation_analysis relation_closure relation_io relation_report)) + +ANALYZER_OBJECTS := $(addprefix $(ANALYZER_OBJECT_DIR)/src/, \ + $(addsuffix .o,$(APP_SOURCE_NAMES))) + +RELEASE_TEST_OBJECTS := $(addprefix $(RELEASE_OBJECT_DIR)/tests/, \ + $(addsuffix .o,$(TEST_SOURCE_NAMES))) +SANITIZER_TEST_OBJECTS := $(addprefix $(SANITIZER_OBJECT_DIR)/tests/, \ + $(addsuffix .o,$(TEST_SOURCE_NAMES))) +COVERAGE_TEST_OBJECTS := $(addprefix $(COVERAGE_OBJECT_DIR)/tests/, \ + $(addsuffix .o,$(TEST_SOURCE_NAMES))) +DEPENDENCY_FILES := $(APP_OBJECTS:.o=.d) $(RELEASE_TEST_OBJECTS:.o=.d) \ + $(SANITIZER_APP_OBJECTS:.o=.d) $(SANITIZER_TEST_OBJECTS:.o=.d) \ + $(COVERAGE_APP_OBJECTS:.o=.d) $(COVERAGE_TEST_OBJECTS:.o=.d) \ + $(ANALYZER_OBJECTS:.o=.d) + +.PHONY: all test sanitize coverage analyze format check-format clean all: $(TARGET) -$(TARGET): $(APP_SOURCES) $(HEADERS) - $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(CFLAGS) $(APP_SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ - -$(SANITIZER_TARGET): $(APP_SOURCES) $(HEADERS) - $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(SANITIZER_FLAGS) $(APP_SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ - - -$(RELATION_TEST_TARGET): tests/test_relation.c $(RELATION_SOURCE) include/relation.h - $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(CFLAGS) tests/test_relation.c $(RELATION_SOURCE) $(LDFLAGS) $(LDLIBS) -o $@ - -$(SANITIZER_RELATION_TEST_TARGET): tests/test_relation.c $(RELATION_SOURCE) include/relation.h - $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(SANITIZER_FLAGS) tests/test_relation.c $(RELATION_SOURCE) $(LDFLAGS) $(LDLIBS) -o $@ - -$(ANALYSIS_TEST_TARGET): tests/test_analysis.c $(ANALYSIS_SOURCES) $(HEADERS) - $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(CFLAGS) tests/test_analysis.c $(ANALYSIS_SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ - -$(SANITIZER_ANALYSIS_TEST_TARGET): tests/test_analysis.c $(ANALYSIS_SOURCES) $(HEADERS) - $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(SANITIZER_FLAGS) tests/test_analysis.c $(ANALYSIS_SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ - -$(EXHAUSTIVE_TEST_TARGET): tests/test_exhaustive.c $(ANALYSIS_SOURCES) $(HEADERS) - $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(CFLAGS) tests/test_exhaustive.c $(ANALYSIS_SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ - -$(SANITIZER_EXHAUSTIVE_TEST_TARGET): tests/test_exhaustive.c $(ANALYSIS_SOURCES) $(HEADERS) - $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(SANITIZER_FLAGS) tests/test_exhaustive.c $(ANALYSIS_SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ - -$(IO_REPORT_TEST_TARGET): tests/test_io_report.c $(IO_REPORT_SOURCES) $(HEADERS) - $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(CFLAGS) tests/test_io_report.c $(IO_REPORT_SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ - -$(SANITIZER_IO_REPORT_TEST_TARGET): tests/test_io_report.c $(IO_REPORT_SOURCES) $(HEADERS) - $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(SANITIZER_FLAGS) tests/test_io_report.c $(IO_REPORT_SOURCES) $(LDFLAGS) $(LDLIBS) -o $@ - -test: $(TARGET) $(RELATION_TEST_TARGET) $(ANALYSIS_TEST_TARGET) $(EXHAUSTIVE_TEST_TARGET) \ - $(IO_REPORT_TEST_TARGET) +$(RELEASE_OBJECT_DIR)/src/%.o: src/%.c + @mkdir -p $(@D) + $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(CFLAGS) \ + $(DEPENDENCY_FLAGS) -c $< -o $@ + +$(RELEASE_OBJECT_DIR)/tests/%.o: tests/%.c + @mkdir -p $(@D) + $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(CFLAGS) \ + $(DEPENDENCY_FLAGS) -c $< -o $@ + +$(SANITIZER_OBJECT_DIR)/src/%.o: src/%.c + @mkdir -p $(@D) + $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(SANITIZER_FLAGS) \ + $(DEPENDENCY_FLAGS) -c $< -o $@ + +$(SANITIZER_OBJECT_DIR)/tests/%.o: tests/%.c + @mkdir -p $(@D) + $(CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(SANITIZER_FLAGS) \ + $(DEPENDENCY_FLAGS) -c $< -o $@ + +$(COVERAGE_OBJECT_DIR)/src/%.o: src/%.c + @mkdir -p $(@D) + $(COVERAGE_CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(COVERAGE_FLAGS) \ + $(DEPENDENCY_FLAGS) -c $< -o $@ + +$(COVERAGE_OBJECT_DIR)/tests/%.o: tests/%.c + @mkdir -p $(@D) + $(COVERAGE_CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(COVERAGE_FLAGS) \ + $(DEPENDENCY_FLAGS) -c $< -o $@ + +$(ANALYZER_OBJECT_DIR)/src/%.o: src/%.c + @mkdir -p $(@D) + $(ANALYZER_CC) $(CPPFLAGS) -Iinclude $(STANDARD_FLAGS) $(WARNING_FLAGS) $(ANALYZER_FLAGS) \ + $(DEPENDENCY_FLAGS) -c $< -o $@ + +$(TARGET): $(APP_OBJECTS) + $(CC) $(APP_OBJECTS) $(LDFLAGS) $(LDLIBS) -o $@ + +$(SANITIZER_TARGET): $(SANITIZER_APP_OBJECTS) + @mkdir -p $(@D) + $(CC) $(SANITIZER_FLAGS) $(SANITIZER_APP_OBJECTS) $(LDFLAGS) $(LDLIBS) -o $@ + +$(COVERAGE_TARGET): $(COVERAGE_APP_OBJECTS) + @mkdir -p $(@D) + $(COVERAGE_CC) $(COVERAGE_FLAGS) $(COVERAGE_APP_OBJECTS) $(LDFLAGS) $(LDLIBS) -o $@ + +$(RELATION_TEST_TARGET): $(RELEASE_OBJECT_DIR)/tests/test_relation.o $(RELATION_OBJECT) + @mkdir -p $(@D) + $(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@ + +$(ANALYSIS_TEST_TARGET): $(RELEASE_OBJECT_DIR)/tests/test_analysis.o $(ANALYSIS_OBJECTS) + @mkdir -p $(@D) + $(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@ + +$(EXHAUSTIVE_TEST_TARGET): $(RELEASE_OBJECT_DIR)/tests/test_exhaustive.o $(ANALYSIS_OBJECTS) + @mkdir -p $(@D) + $(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@ + +$(IO_REPORT_TEST_TARGET): $(RELEASE_OBJECT_DIR)/tests/test_io_report.o $(IO_REPORT_OBJECTS) + @mkdir -p $(@D) + $(CC) $^ $(LDFLAGS) $(LDLIBS) -o $@ + +$(SANITIZER_RELATION_TEST_TARGET): $(SANITIZER_OBJECT_DIR)/tests/test_relation.o \ + $(SANITIZER_RELATION_OBJECT) + @mkdir -p $(@D) + $(CC) $(SANITIZER_FLAGS) $^ $(LDFLAGS) $(LDLIBS) -o $@ + +$(SANITIZER_ANALYSIS_TEST_TARGET): $(SANITIZER_OBJECT_DIR)/tests/test_analysis.o \ + $(SANITIZER_ANALYSIS_OBJECTS) + @mkdir -p $(@D) + $(CC) $(SANITIZER_FLAGS) $^ $(LDFLAGS) $(LDLIBS) -o $@ + +$(SANITIZER_EXHAUSTIVE_TEST_TARGET): $(SANITIZER_OBJECT_DIR)/tests/test_exhaustive.o \ + $(SANITIZER_ANALYSIS_OBJECTS) + @mkdir -p $(@D) + $(CC) $(SANITIZER_FLAGS) $^ $(LDFLAGS) $(LDLIBS) -o $@ + +$(SANITIZER_IO_REPORT_TEST_TARGET): $(SANITIZER_OBJECT_DIR)/tests/test_io_report.o \ + $(SANITIZER_IO_REPORT_OBJECTS) + @mkdir -p $(@D) + $(CC) $(SANITIZER_FLAGS) $^ $(LDFLAGS) $(LDLIBS) -o $@ + +$(COVERAGE_RELATION_TEST_TARGET): $(COVERAGE_OBJECT_DIR)/tests/test_relation.o \ + $(COVERAGE_RELATION_OBJECT) + @mkdir -p $(@D) + $(COVERAGE_CC) $(COVERAGE_FLAGS) $^ $(LDFLAGS) $(LDLIBS) -o $@ + +$(COVERAGE_ANALYSIS_TEST_TARGET): $(COVERAGE_OBJECT_DIR)/tests/test_analysis.o \ + $(COVERAGE_ANALYSIS_OBJECTS) + @mkdir -p $(@D) + $(COVERAGE_CC) $(COVERAGE_FLAGS) $^ $(LDFLAGS) $(LDLIBS) -o $@ + +$(COVERAGE_EXHAUSTIVE_TEST_TARGET): $(COVERAGE_OBJECT_DIR)/tests/test_exhaustive.o \ + $(COVERAGE_ANALYSIS_OBJECTS) + @mkdir -p $(@D) + $(COVERAGE_CC) $(COVERAGE_FLAGS) $^ $(LDFLAGS) $(LDLIBS) -o $@ + +$(COVERAGE_IO_REPORT_TEST_TARGET): $(COVERAGE_OBJECT_DIR)/tests/test_io_report.o \ + $(COVERAGE_IO_REPORT_OBJECTS) + @mkdir -p $(@D) + $(COVERAGE_CC) $(COVERAGE_FLAGS) $^ $(LDFLAGS) $(LDLIBS) -o $@ + +test: $(TARGET) $(TEST_TARGETS) ./$(RELATION_TEST_TARGET) ./$(ANALYSIS_TEST_TARGET) ./$(EXHAUSTIVE_TEST_TARGET) ./$(IO_REPORT_TEST_TARGET) ./tests/run_tests.sh ./$(TARGET) -sanitize: $(SANITIZER_TARGET) $(SANITIZER_RELATION_TEST_TARGET) \ - $(SANITIZER_ANALYSIS_TEST_TARGET) $(SANITIZER_EXHAUSTIVE_TEST_TARGET) \ - $(SANITIZER_IO_REPORT_TEST_TARGET) +sanitize: $(SANITIZER_TARGET) $(SANITIZER_TEST_TARGETS) ASAN_OPTIONS=detect_leaks=$${ASAN_DETECT_LEAKS:-0} ./$(SANITIZER_RELATION_TEST_TARGET) ASAN_OPTIONS=detect_leaks=$${ASAN_DETECT_LEAKS:-0} ./$(SANITIZER_ANALYSIS_TEST_TARGET) ASAN_OPTIONS=detect_leaks=$${ASAN_DETECT_LEAKS:-0} ./$(SANITIZER_EXHAUSTIVE_TEST_TARGET) ASAN_OPTIONS=detect_leaks=$${ASAN_DETECT_LEAKS:-0} ./$(SANITIZER_IO_REPORT_TEST_TARGET) ASAN_OPTIONS=detect_leaks=$${ASAN_DETECT_LEAKS:-0} ./tests/run_tests.sh ./$(SANITIZER_TARGET) +coverage: $(COVERAGE_TARGET) $(COVERAGE_TEST_TARGETS) + ./$(COVERAGE_RELATION_TEST_TARGET) + ./$(COVERAGE_ANALYSIS_TEST_TARGET) + ./$(COVERAGE_EXHAUSTIVE_TEST_TARGET) + ./$(COVERAGE_IO_REPORT_TEST_TARGET) + ./tests/run_tests.sh ./$(COVERAGE_TARGET) + $(GCOV) --branch-counts --branch-probabilities --function-summaries --no-output \ + --object-directory $(COVERAGE_OBJECT_DIR)/src $(APP_SOURCES) + +analyze: $(ANALYZER_OBJECTS) + +format: + @command -v $(CLANG_FORMAT) >/dev/null 2>&1 || \ + { echo "Error: $(CLANG_FORMAT) is required for formatting" >&2; exit 1; } + $(CLANG_FORMAT) -i $(FORMAT_SOURCES) + +check-format: + @command -v $(CLANG_FORMAT) >/dev/null 2>&1 || \ + { echo "Error: $(CLANG_FORMAT) is required for format checks" >&2; exit 1; } + $(CLANG_FORMAT) --dry-run --Werror $(FORMAT_SOURCES) + clean: - rm -f $(TARGET) $(SANITIZER_TARGET) $(RELATION_TEST_TARGET) \ - $(SANITIZER_RELATION_TEST_TARGET) $(ANALYSIS_TEST_TARGET) \ - $(SANITIZER_ANALYSIS_TEST_TARGET) $(EXHAUSTIVE_TEST_TARGET) \ - $(SANITIZER_EXHAUSTIVE_TEST_TARGET) $(IO_REPORT_TEST_TARGET) \ - $(SANITIZER_IO_REPORT_TEST_TARGET) + rm -rf $(BUILD_DIR) + rm -f $(TARGET) + +# Compiler-generated dependency files keep incremental builds header-aware. +-include $(DEPENDENCY_FILES) diff --git a/src/relation_report.c b/src/relation_report.c index 82f24f1..09fe7c7 100644 --- a/src/relation_report.c +++ b/src/relation_report.c @@ -44,8 +44,7 @@ static void write_reflexivity_details(const Relation *relation, FILE *output) { fputs("Missing reflexive pairs: ", output); for (index = 0; index < relation_size(relation); index++) { if (!relation_contains(relation, index, index)) { - fprintf(output, "(%d,%d); ", element_at(relation, index), - element_at(relation, index)); + fprintf(output, "(%d,%d); ", element_at(relation, index), element_at(relation, index)); } } fputc('\n', output); @@ -57,8 +56,7 @@ static void write_irreflexivity_details(const Relation *relation, FILE *output) fputs("Irreflexive counterexamples: ", output); for (index = 0; index < relation_size(relation); index++) { if (relation_contains(relation, index, index)) { - fprintf(output, "(%d,%d); ", element_at(relation, index), - element_at(relation, index)); + fprintf(output, "(%d,%d); ", element_at(relation, index), element_at(relation, index)); } } fputc('\n', output); diff --git a/tests/test_analysis.c b/tests/test_analysis.c index 2636e01..e299cdd 100644 --- a/tests/test_analysis.c +++ b/tests/test_analysis.c @@ -5,8 +5,8 @@ #include "relation_analysis.h" #include "relation_closure.h" -static Relation *create_relation(const int *elements, size_t element_count, - const int (*pairs)[2], size_t pair_count) { +static Relation *create_relation(const int *elements, size_t element_count, const int (*pairs)[2], + size_t pair_count) { Relation *relation = relation_create(elements, element_count); size_t index; diff --git a/tests/test_exhaustive.c b/tests/test_exhaustive.c index 7150146..4becd8f 100644 --- a/tests/test_exhaustive.c +++ b/tests/test_exhaustive.c @@ -156,8 +156,8 @@ static void assert_analysis_equal(RelationAnalysis actual, RelationAnalysis expe assert(actual.partial_order == expected.partial_order); } -static void assert_relation_equals_matrix(const Relation *relation, - ReferenceMatrix expected, size_t size) { +static void assert_relation_equals_matrix(const Relation *relation, ReferenceMatrix expected, + size_t size) { size_t source; size_t target; From bfd36581f9d3d300892de427060433e59da8eb50 Mon Sep 17 00:00:00 2001 From: Lucas Mariz Date: Sat, 12 Sep 2026 12:37:17 -0300 Subject: [PATCH 6/6] docs: rewrite project documentation in English --- README.md | 252 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 164 insertions(+), 88 deletions(-) diff --git a/README.md b/README.md index 77abb3d..1f1bfed 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,166 @@ -/* -Trabalho desenvolvido por Lucas Paulo Martins Mariz - Técnico em informática formado pelo Coltec/UFMG e aluno de Ciência da Computação UFMG -*/ -# Relações Binárias -### Considerações Iniciais: -O projeto foi inteiramente desenvolvido em um ambiente linux, sendo assim, o melhor sistema para sua execução sem grandes problemas é o Linux (preferencialmente Ubuntu). -Para a execução é necessário instalar alguns pacotes relacionados ao makefile, caso já não estejam instalados. - - $ sudo apt-get update - $ sudo apt-get upgrade - $ sudo apt-get install make - -Após a instalação, basta digitar ir para o diretorio do trabalho utilizando o comando "cd" e digitar o comando "make" no terminal, o qual irá compilar o main e a biblioteca: - - $ make - -A biblioteca será automaticamente compilada e o programa irá gerar um arquivo executável "a.out", que deverá ser executado seguindo da seguinte forma: - - $ ./a.out - -A execução do projeto é independente e não necessita da interferência do usuário pois todos os dados são resgatados do arquivo "dados.txt". - -Para modificar as informações do programa basta alterar o arquivo "dados.txt". Ele está formatado da seguinte forma: - -| Linha | Digito | Propriedade | -| ---------------- |:---------------- |:-------------------------------------------:| -| 1° | 1° Digito | número de elementos do grafo | -| 1° | Digitos Seguintes | nome de cada elemento | -|Linhas Seguintes | Digitos Seguintes | relacionamentos entre os elementos do grafo | - -- Arquivos: - -| Arquivo | Função | -| ------------- |:-----------------------------------------------------------------------------:| -| main.c | main do trabalho | -| grafo.c | biblioteca com todas as funções para determinar as propriedades dos grafos | -| grafo.h | linkagem das bibliotecas | -| makefile | arquivo make para compilar o main.c e o grafo.c | -| dados.txt | arquivo com a entrada do trabalho (numero de elementos, elementos, relações) | -| rb.pfd | documentação do trabalho | - -# Documentação: -### Descrição Geral: -O desenvolvimento de uma estrutura na linguagem C na qual as relações seriam armazenadas em um grafo se torna uma tarefa um tanto quanto complexa dado o fato de ser necessário o uso de ponteiros de ponteiros, arquivos, vetores diâmicos, entre outros. Sendo assim, todo o processo foi minuciosamente esquematizado para evitar que erros de lógica pudessem vir a surgir. -### Arquivo grafo.c e suas funções: -Com o intuito de deixar o trabalho mais organizado, todas as funções designadas a determinar propriedades das relações foram feitas em um arquivo separado. Tal arquivo pode ser chamado como uma biblioteca, a qual é incluída através de seu respectivo arquivo ".h". -A estrutura principal do grafo consiste em alguns elementos e propriedades devidamente expliciadas. As propriedades podem assumir os valores 1 ou 0, dependendo da sua ocorrência ou não. Por padrão lógico, todas as propriedades são consideradas verdadeiras até que se prove ao contrário. -```c -struct grafo { - int **matriz_adjacencia; // Matriz de adjacencias que representa o grafo - int n_vertices; // Número total de vertices que o grafo terá - int elementos[50]; // Nomes dos vértices fornecidos pelo usuário - int n_ligacoes; // Número total de ligações que o grafo fará - - // Propriedades: 1 -> V | 0 -> F - int propriedade_reflexiva; - int propriedade_irreflexiva; - int propriedade_simetrica; - int propriedade_anti_simetrica; - int propriedade_assimetrica; - int propriedade_transitiva; - - int relacao_equivalencia; - int relacao_ordem_parcial; -}; +# Binary Relation Analyzer + +Binary Relation Analyzer is a dependency-free C17 command-line application for inspecting finite +binary relations. It reads a set and its ordered pairs from a text file, displays the corresponding +matrix, evaluates common relation properties, and computes reflexive, symmetric, and transitive +closures. + +## Features + +- Evaluates reflexivity, irreflexivity, symmetry, antisymmetry, asymmetry, and transitivity. +- Identifies equivalence relations and partial orders. +- Reports counterexamples for reflexivity, irreflexivity, symmetry, antisymmetry, and transitivity. +- Computes minimal reflexive, symmetric, and transitive supersets. +- Accepts signed integer labels and ignores repeated ordered pairs. +- Handles invalid input with line-specific diagnostics and a nonzero exit status. +- Includes focused unit tests, CLI integration tests, and exhaustive verification of every relation + over sets containing up to three elements. + +## Requirements + +To build and run the application, you need: + +- a C17 compiler such as GCC or Clang; +- GNU Make; +- a POSIX-compatible shell for the integration tests. + +Optional development commands also use GCC, gcov, and clang-format. + +## Build and run + +Build the release executable: + +```console +$ make +``` + +Analyze the bundled example: + +```console +$ ./binary-relation-analyzer examples/sample.txt ``` -A Biblioteca é constituída pelas seguintes funções: -```c - Grafo* cria_grafo(int *excessao_zero); // Alocação dinâmica da matriz de adjacências - Grafo* preenche_grafo(int *excessao_zero); // Preenchimento da matriz com os dados do arquivo - void insere_aresta(Grafo *gr, int orig, int dest); // Tratamento da informação para a inserção - void imprime_matriz(Grafo *gr); // Impressão da matriz de adjacências - void inicializar(); // Função chamada pelo main.c; inicia toda a execução - void propriedades(Grafo *gr); // Central de definição das propriedades do grafo - void libera_matriz(int **m, int tam); // Liberação do espaço alocado - int** aloca_matriz(int tam); // Aloca matriz inteira quadrada - void reflexiva(Grafo *gr); // Determina se a relação e reflexiva ou não - void irreflexiva(Grafo *gr); // Determina se a relação é irreflexiva ou não - void simetrica(Grafo *gr); // Determina se a relação é simétrica ou não - void anti_simetrica(Grafo *gr); // Determina se a relação é anti-simétrica ou não - void assimetrica(Grafo *gr); // Determina se a relação é assimétrica ou não - void transitiva(Grafo *gr); // Determina se a relação é transitiva ou não - void equivalencia(Grafo *gr); // Determina se a relação é de equivalência - void ordem_parcial(Grafo *gr); // Determina se a relação é de ordem parcial - void fecho_reflexivo(Grafo *gr); // Determina o fecho reflexivo da relação - void fecho_simetrico(Grafo *gr); // Determina o fecho simétrico da relação - void fecho_transitivo(Grafo *gr); // Determina o fecho transitivo da relação - void fecho_padrao(Grafo *gr); // Determina o fecho padrão da relação + +The report contains the relation matrix, the result of each property check, relevant +counterexamples, and all supported closures. + +Display the command-line help with: + +```console +$ ./binary-relation-analyzer --help ``` -A variavel "excessao_zero" entra em ação quando inserimos um grafo com zero vértices, fazendo todas as propriedades serem verdadeiras por default. + +Use `--` before a filename that begins with a dash: + +```console +$ ./binary-relation-analyzer -- -relation.txt +``` + +## Input format + +The first line declares the number of elements followed by exactly that many unique integer +labels. Each subsequent non-empty line contains one ordered pair: + +```text +3 10 20 30 +10 20 +20 30 +``` + +This input represents the set `{10, 20, 30}` and the relation `{(10, 20), (20, 30)}`. + +Input rules: + +- A set may contain from 0 to 50 elements. +- Labels must be unique signed integers representable by the platform's C `int` type. +- Whitespace separates values, and blank lines after the header are ignored. +- Every ordered-pair member must belong to the declared set. +- Each pair must occupy its own line and contain exactly two labels. +- Repeated pairs are accepted but do not change the relation. +- Missing, extra, malformed, unknown, or duplicate element values invalidate the entire input. + +An empty relation over an empty set can be written as: + +```text +0 +``` + +## Properties + +For a relation `R` over a finite set `A`, the analyzer uses the following definitions: + +| Property | Condition | +| --- | --- | +| Reflexive | For every `a` in `A`, `(a, a)` belongs to `R`. | +| Irreflexive | For every `a` in `A`, `(a, a)` does not belong to `R`. | +| Symmetric | If `(a, b)` belongs to `R`, then `(b, a)` belongs to `R`. | +| Antisymmetric | If `(a, b)` and `(b, a)` belong to `R`, then `a = b`. | +| Asymmetric | If `(a, b)` belongs to `R`, then `(b, a)` does not belong to `R`. | +| Transitive | If `(a, b)` and `(b, c)` belong to `R`, then `(a, c)` belongs to `R`. | +| Equivalence relation | `R` is reflexive, symmetric, and transitive. | +| Partial order | `R` is reflexive, antisymmetric, and transitive. | + +Properties of the empty set follow vacuous-truth semantics. Its empty relation therefore satisfies +all six base properties and is both an equivalence relation and a partial order. + +Each closure is a new relation containing every original pair plus the minimum pairs required to +satisfy the corresponding property. The transitive closure is computed with Warshall's algorithm. + +## Development commands + +| Command | Purpose | +| --- | --- | +| `make test` | Build and run unit, exhaustive, integration, parser, and CLI tests. | +| `make sanitize` | Run the complete suite with AddressSanitizer and UBSan. | +| `make coverage` | Run instrumented tests and print gcov line, branch, and function coverage. | +| `make analyze` | Compile all application modules with the GCC static analyzer. | +| `make format` | Format all C sources and public headers with clang-format. | +| `make check-format` | Fail if any C source or public header is not correctly formatted. | +| `make clean` | Remove the executable and all generated build artifacts. | + +LeakSanitizer is disabled by default because it is not supported in some traced or sandboxed +environments. Enable it on a compatible host with: + +```console +$ ASAN_DETECT_LEAKS=1 make sanitize +``` + +Override the release compiler when needed: + +```console +$ make clean +$ make CC=clang test +``` + +Generated objects, dependency files, test executables, and instrumented binaries are isolated under +`build/`. The release executable remains at the repository root. + +## Project structure + +```text +. +├── examples/ Sample input files +├── include/ Public module interfaces +├── src/ +│ ├── main.c Process entry point +│ ├── application.c Command-line parsing and application lifecycle +│ ├── relation.c Relation domain model and pair storage +│ ├── relation_analysis.c Pure property evaluation +│ ├── relation_closure.c Closure algorithms +│ ├── relation_io.c Text input parsing and validation +│ └── relation_report.c Deterministic report formatting +└── tests/ Unit, exhaustive, fixture, and integration tests +``` + +The `Relation` type is opaque. Analysis functions do not modify it, and each closure function +returns an independently owned relation. This separation keeps parsing, domain logic, algorithms, +and presentation testable without invoking the full application. + +## Continuous integration + +GitHub Actions builds and tests the project with GCC and Clang on every push and pull request. A +separate sanitizer job checks memory and undefined behavior, while the quality job verifies source +formatting, runs GCC static analysis, and collects coverage. + +## Author + +Created by Lucas Paulo Martins Mariz.