Program Listing for File parsing.tests.cpp

Return to documentation for file (src/da4cpp/parsing.tests.cpp)

#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include <doctest/doctest.h>

#include <da4cpp/parsing.hpp>
#include <filesystem>
#include <map>
#include <optional>
#include <ostream>
#include <sstream>
#include <string>
#include <tuple>

using da4cpp::graph::Dependencies;
using da4cpp::graph::DependencyGraph;
using da4cpp::graph::Symbol;
using da4cpp::graph::SymbolType;
using da4cpp::parsing::parse_translation_unit;

struct SymbolMatch {
  std::string filepath;
  SymbolType symbolType;
  std::string name;

  bool operator()(const Symbol& symbol) const {
    return symbol.filePath == filepath && symbol.name == name && symbol.symbolType == symbolType;
  }
};

namespace {

  std::filesystem::path resources_dir() { return std::filesystem::path{TEST_BINARY_DIR} / "parsing.tests.resources"; }

  using TestParameters = std::tuple<std::filesystem::path, DependencyGraph>;

  std::ostream& operator<<(std::ostream& oss, const Symbol& symbol) {
    const std::map<SymbolType, std::string> representation{{SymbolType::TypeDefinition, "TypeDefinition"},
                                                           {SymbolType::TypeDeclaration, "TypeDeclaration"},
                                                           {SymbolType::FunctionDefinition, "FunctionDefinition"},
                                                           {SymbolType::FunctionDeclaration, "FunctionDeclaration"},
                                                           {SymbolType::VariableDefinition, "VariableDefinition"},
                                                           {SymbolType::VariableDeclaration, "VariableDeclaration"}};

    oss << symbol.name << " - " << representation.at(symbol.symbolType) << " (" << symbol.filePath.filename().string()
        << ":" << symbol.line << "," << symbol.column << ")";
    return oss;
  }

  std::ostream& operator<<(std::ostream& oss, const Dependencies& dependencies) {
    oss << "{";
    for (auto it = dependencies.begin(); it != dependencies.end(); ++it) {
      if (it != dependencies.begin()) {
        oss << ", ";
      }
      oss << *it;
    }
    oss << "}";
    return oss;
  }

  std::string compare_maps(const DependencyGraph& actual, const DependencyGraph& expected) {
    std::ostringstream oss;
    for (const auto& [symbol, dependencies] : actual) {
      if (!expected.contains(symbol)) {
        oss << "Extra symbol in actual" << std::endl
            << symbol << std::endl
            << "with dependencies" << std::endl
            << dependencies << std::endl;
      } else if (expected.at(symbol) != dependencies) {
        oss << "Mismatched dependencies for symbol " << std::endl
            << symbol << std::endl
            << "where actual =" << std::endl
            << dependencies << std::endl
            << "and expected = " << std::endl
            << expected.at(symbol) << std::endl;
      }
    }

    for (const auto& [symbol, dependencies] : expected) {
      if (!actual.contains(symbol)) {
        oss << "Missing symbol in actual" << std::endl
            << symbol << std::endl
            << "with dependencies" << std::endl
            << dependencies << std::endl;
      }
    }

    return oss.str();
  }

  void check_dependency_graph(const std::filesystem::path& resourcePath, const DependencyGraph& expectedGraph) {
    std::optional<DependencyGraph> graphOption = parse_translation_unit(resourcePath);

    if (graphOption.has_value()) {
      const DependencyGraph& actualGraph = graphOption.value();
      INFO(compare_maps(actualGraph, expectedGraph));
      REQUIRE((expectedGraph == actualGraph));
    } else {
      FAIL("parse_translation_unit failed to parse" << resourcePath.string());
    }
  }

  auto get_symbol_constructor(const std::filesystem::path& resourcePath) {
    return [resourcePath](unsigned int line, unsigned int column, const std::string& name, SymbolType symbolType) {
      return Symbol{.filePath = resourcePath, .line = line, .column = column, .name = name, .symbolType = symbolType};
    };
  }
}  // namespace

TEST_CASE("function_call") {
  const std::filesystem::path resourcePath{resources_dir() / "function_call.cpp"};
  auto symbol = get_symbol_constructor(resourcePath);

  const Symbol functionDefinition{symbol(1, 6, "function", SymbolType::FunctionDefinition)};
  const Symbol mainDefinition{symbol(3, 5, "main", SymbolType::FunctionDefinition)};

  const DependencyGraph expectedGraph{{functionDefinition, {}}, {mainDefinition, {functionDefinition}}};

  check_dependency_graph(resourcePath, expectedGraph);
}

TEST_CASE("struct_reference") {
  const std::filesystem::path resourcePath{resources_dir() / "struct_reference.cpp"};
  auto symbol = get_symbol_constructor(resourcePath);

  const Symbol structDefinition{symbol(4, 8, "Structure", SymbolType::TypeDefinition)};
  const Symbol mainDefinition{symbol(11, 5, "main", SymbolType::FunctionDefinition)};

  const DependencyGraph expectedGraph{{structDefinition, {}}, {mainDefinition, {structDefinition}}};

  check_dependency_graph(resourcePath, expectedGraph);
}

TEST_CASE("struct_forward_declaration") {
  const std::filesystem::path resourcePath{resources_dir() / "struct_forward_declaration.cpp"};
  auto symbol = get_symbol_constructor(resourcePath);

  const Symbol definitionA{symbol(7, 8, "A", SymbolType::TypeDefinition)};
  const Symbol declarationB{symbol(2, 8, "B", SymbolType::TypeDeclaration)};

  const DependencyGraph expectedGraph{{definitionA, {declarationB}}, {declarationB, {}}};

  check_dependency_graph(resourcePath, expectedGraph);
}