diff --git a/bakefile.jar b/bakefile.jar deleted file mode 100644 index 875b1b3..0000000 Binary files a/bakefile.jar and /dev/null differ diff --git a/src/fr/monlouyan/bakefile/BakeCLI.java b/src/fr/monlouyan/bakefile/BakeCLI.java index b909324..3fba9cc 100644 --- a/src/fr/monlouyan/bakefile/BakeCLI.java +++ b/src/fr/monlouyan/bakefile/BakeCLI.java @@ -8,7 +8,6 @@ import java.util.ArrayList; * * @author Moncef STITI * @version 1.0 - * @date 03/02/2025 * @see Main */ public class BakeCLI { @@ -26,7 +25,6 @@ public class BakeCLI { * Constructeur de la classe BakeCLI * * @param args Les arguments passés en ligne de commande - * @return void * @see Main */ public BakeCLI(String[] args){ @@ -58,7 +56,7 @@ public class BakeCLI { /** * Permet de récupérer les arguments autres que "-d" passés en ligne de commande - * @return + * @return La liste des arguments */ public static List<String> getTargets(){ return targets; } } diff --git a/src/fr/monlouyan/bakefile/BakeEngine.java b/src/fr/monlouyan/bakefile/BakeEngine.java index a536064..22b5a7d 100644 --- a/src/fr/monlouyan/bakefile/BakeEngine.java +++ b/src/fr/monlouyan/bakefile/BakeEngine.java @@ -4,36 +4,83 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +/** + * Moteur principal de l'application Bake. + * Cette classe est responsable de l'orchestration du processus de build + * en utilisant les règles définies dans le fichier Bakefile. + * + * @author Moncef STITI, Yanis HAMOUDI, Louay DARDOURI + * @version 1.0 + */ public class BakeEngine { + + /** Analyseur syntaxique du fichier Bakefile */ private BakefileParser parser; + + /** Responsable de la résolution des dépendances entre règles */ private DependencyResolver resolver; + + /** Exécuteur des commandes définies dans les règles */ private CommandExecutor executor; + + /** Map qui stocke toutes les règles indexées par leur nom */ private static Map<String, Rule> ruleMap; + /** + * Constructeur qui initialise le moteur Bake. + * Crée un parser pour le fichier "Bakefile" par défaut et initialise le résolveur de dépendances. + */ public BakeEngine() { this.parser = new BakefileParser("Bakefile"); this.resolver = new DependencyResolver(BakeCLI.isDebug()); ruleMap = new HashMap<>(); } + /** + * Vérifie si une règle avec le nom spécifié existe. + * + * @param target Nom de la règle à vérifier + * @return true si la règle existe, false sinon + */ public static boolean hasRule(String target) { return ruleMap.containsKey(target); } + + /** + * Récupère une règle par son nom. + * + * @param target Nom de la règle à récupérer + * @return La règle correspondante ou null si elle n'existe pas + */ public static Rule getRule(String target) { return ruleMap.get(target); } + /** + * Exécute le processus de build complet. + * Cette méthode réalise les étapes suivantes : + * <ol> + * <li>Parse le fichier Bakefile</li> + * <li>Construit la carte des règles</li> + * <li>Résout les dépendances entre les règles</li> + * <li>Exécute les règles dans l'ordre déterminé</li> + * </ol> + */ public void run() { + // Analyse le fichier Bakefile pour extraire les règles List<Rule> rules = parser.parse(); + // Ajoute toutes les règles à la map pour référence rapide for (Rule rule : rules) { ruleMap.put(rule.getName(), rule); } + // Résout les dépendances pour déterminer l'ordre d'exécution List<Rule> rulesToBuild = resolver.resolve(rules, BakeCLI.getTargets()); this.executor = new CommandExecutor(BakeCLI.isDebug(), resolver.isCircular()); - + + // Exécute les règles dans l'ordre déterminé for (Rule rule : rulesToBuild) { executor.execute(rule); } diff --git a/src/fr/monlouyan/bakefile/BakefileParser.java b/src/fr/monlouyan/bakefile/BakefileParser.java index 711312a..6f6b039 100644 --- a/src/fr/monlouyan/bakefile/BakefileParser.java +++ b/src/fr/monlouyan/bakefile/BakefileParser.java @@ -8,6 +8,14 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; +/** + * Parseur de fichier Bakefile. + * Cette classe est responsable de l'analyse syntaxique du fichier Bakefile + * pour extraire les règles de build, les dépendances et les commandes associées. + * + * @author Moncef STITI, Yanis HAMOUDI, Louay DARDOURI + * @version 1.0 + */ public class BakefileParser { /** @@ -62,11 +70,21 @@ public class BakefileParser { */ private Map<String, String> variables = new HashMap<>(); + /** + * Constructeur de la classe BakefileParser. + * @param filename Nom du fichier Bakefile à parser + */ public BakefileParser(String filename) { this.filename = filename; firstTarget = null; } + /** + * Gérer les lignes de continuation. + * @param lines Liste des lignes du fichier Bakefile + * @param startIndex Index de la première ligne de continuation + * @return La ligne combinée + */ private String handleContinuationLines(List<String> lines, int startIndex) { StringBuilder combinedLine = new StringBuilder(); int i = startIndex; @@ -89,6 +107,11 @@ public class BakefileParser { return combinedLine.toString(); } + /** + * Remplacer les variables dans une chaîne. + * @param input Chaîne à traiter + * @return Chaîne avec les variables remplacées + */ private String replaceVariables(String input) { if (input == null) return null; @@ -126,12 +149,22 @@ public class BakefileParser { return result.trim(); } + /** + * Remplacer les variables dans une liste de chaînes. + * @param items Liste de chaînes à traiter + * @return Liste de chaînes avec les variables remplacées + */ private List<String> replaceVariablesInList(List<String> items) { return items.stream() .map(this::replaceVariables) .collect(Collectors.toList()); } + /** + * Découper les dépendances en une liste de chaînes. + * @param depStr Chaîne de dépendances + * @return Liste de dépendances + */ private List<String> splitDependencies(String depStr) { if (depStr == null || depStr.trim().isEmpty()) { return new ArrayList<>(); @@ -144,6 +177,11 @@ public class BakefileParser { .collect(Collectors.toList()); } + /** + * Découper les cibles en une liste de chaînes. + * @param targetStr Chaîne de cibles + * @return Liste de cibles + */ private List<String> splitTargets(String targetStr) { if (targetStr == null || targetStr.trim().isEmpty()) { return new ArrayList<>(); @@ -156,6 +194,10 @@ public class BakefileParser { .collect(Collectors.toList()); } + /** + * Analyser le fichier Bakefile pour extraire les règles de build. + * @return Liste des règles extraites + */ public List<Rule> parse() { List<Rule> rules = new ArrayList<>(); Set<String> phonyTargets = new HashSet<>(); diff --git a/src/fr/monlouyan/bakefile/CommandExecutor.java b/src/fr/monlouyan/bakefile/CommandExecutor.java index 4d1287e..5aeaa02 100644 --- a/src/fr/monlouyan/bakefile/CommandExecutor.java +++ b/src/fr/monlouyan/bakefile/CommandExecutor.java @@ -3,17 +3,49 @@ package fr.monlouyan.bakefile; import java.io.File; import java.io.IOException; +/** + * Exécuteur des commandes définies dans les règles. + * Cette classe est responsable de l'exécution des commandes définies dans les règles + * du fichier Bakefile. + * + * @author Moncef STITI, Yanis HAMOUDI, Louay DARDOURI + * @version 1.0 + */ public class CommandExecutor { + /** + * true si le mode debug est activé, false sinon + */ private boolean debug; - private boolean needsUpdate = false; // Pour tracker si quelque chose doit être mis à jour - private boolean isCircular = false; // Pour tracker si un cycle a été détecté - private boolean futureTimestampDetected = false; // Pour détecter les timestamps dans le futur + /** + * Pour tracker si quelque chose doit être mis à jour + */ + private boolean needsUpdate = false; + + /** + * Pour tracker si un cycle a été détecté + */ + private boolean isCircular = false; + + /** + * Pour détecter les timestamps dans le futur + */ + private boolean futureTimestampDetected = false; + + /** + * Constructeur de la classe CommandExecutor. + * @param debug true si le mode debug est activé, false sinon + * @param isCircular true si on est en mode circulaire, false sinon + */ public CommandExecutor(boolean debug, boolean isCircular) { this.debug = debug; this.isCircular = isCircular; } + /** + * Exécute les commandes d'une règle. + * @param rule La règle à exécuter + */ public void execute(Rule rule) { // On vérifie d'abord si cette règle a besoin d'être mise à jour boolean ruleNeedsUpdate = rule.needsUpdate(); diff --git a/src/fr/monlouyan/bakefile/DependencyResolver.java b/src/fr/monlouyan/bakefile/DependencyResolver.java index 26c5378..b2574b4 100644 --- a/src/fr/monlouyan/bakefile/DependencyResolver.java +++ b/src/fr/monlouyan/bakefile/DependencyResolver.java @@ -2,16 +2,40 @@ package fr.monlouyan.bakefile; import java.util.*; +/** + * Résolveur de dépendances entre les règles du fichier Bakefile. + * Cette classe est responsable de la résolution des dépendances entre les règles + * pour déterminer l'ordre dans lequel les règles doivent être + * + * @author Moncef STITI, Yanis HAMOUDI, Louay DARDOURI + * @version 1.0 + */ public class DependencyResolver { + + /*** true si le mode debug est activé, false sinon */ private boolean debug; + + /*** Map des règles indexées par leur nom */ private Map<String, Rule> ruleMap; + + /*** true si une dépendance circulaire est détectée, false sinon */ private boolean isCircular; - + + /** + * Constructeur de la classe DependencyResolver. + * @param debug true si le mode debug est activé, false sinon + */ public DependencyResolver(boolean debug) { this.debug = debug; this.isCircular = false; } + /** + * Résout les dépendances entre les règles. + * @param allRules Liste de toutes les règles + * @param requestedRules Liste des règles demandées + * @return Liste + */ public List<Rule> resolve(List<Rule> allRules, List<String> requestedRules) { List<Rule> rulesToBuild = new ArrayList<>(); ruleMap = new HashMap<>(); @@ -64,6 +88,12 @@ public class DependencyResolver { return rulesToBuild; } + /** + * Méthode de tri topologique pour résoudre les dépendances. + * @param ruleName Nom de la règle + * @param processed Set des règles déjà traitées + * @param buildOrder Liste des règles dans l'ordre + */ private void topologicalSort(String ruleName, Set<String> processed, List<String> buildOrder) { if (!ruleMap.containsKey(ruleName) || processed.contains(ruleName)) { return; @@ -85,7 +115,13 @@ public class DependencyResolver { buildOrder.add(ruleName); } - // La méthode detectCycle avec une modification pour les chemins home + /** + * Méthode de détection des cycles dans les dépendances. + * @param ruleName Nom de la règle + * @param visited Set des règles déjà visitées + * @param stack Set des règles en cours de traitement + * @param parent Nom de la règle parente + */ private void detectCycle(String ruleName, Set<String> visited, Set<String> stack, String parent) { if (stack.contains(ruleName)) { if (parent != null) { @@ -121,6 +157,10 @@ public class DependencyResolver { } } + /** + * Vérifie si une dépendance circulaire a été détectée. + * @return true si une dépendance circulaire a été détectée, false sinon + */ public boolean isCircular() { return isCircular; } diff --git a/src/fr/monlouyan/bakefile/Main.java b/src/fr/monlouyan/bakefile/Main.java index 3f455f1..ee4e909 100755 --- a/src/fr/monlouyan/bakefile/Main.java +++ b/src/fr/monlouyan/bakefile/Main.java @@ -5,15 +5,20 @@ package fr.monlouyan.bakefile; * * @version 1.0 * @author Moncef STITI - * @date 03/02/2025 */ public class Main{ + /** + * Constructeur de la classe Main (privé pour empêcher l'instanciation) + */ + private Main() { + // Constructeur inutile + } + /** * Méthode principale du programme * * @param args Les arguments passés en ligne de commande - * @return void */ public static void main(String[] args){ @SuppressWarnings("unused") diff --git a/src/fr/monlouyan/bakefile/Rule.java b/src/fr/monlouyan/bakefile/Rule.java index 2a1d074..f2f3648 100644 --- a/src/fr/monlouyan/bakefile/Rule.java +++ b/src/fr/monlouyan/bakefile/Rule.java @@ -2,14 +2,36 @@ package fr.monlouyan.bakefile; import java.io.File; import java.util.List; -import java.util.Date; +/** + * Représente une règle du fichier Bakefile. + * Chaque règle est composée d'un nom, d'une liste de dépendances et d'une liste de commandes. + * Une règle peut être phony, c'est-à-dire qu'elle n'a pas de fichier cible associé. + * + * @author Moncef STITI, Yanis HAMOUDI, Louay DARDOURI + * @version 1.0 + */ public class Rule { + + /*** Nom de la règle */ private String name; + + /*** Liste des dépendances de la règle */ private List<String> dependencies; + + /*** Liste des commandes */ private List<String> commands; + + /*** true si la règle est phony, false sinon */ private boolean isPhony; + /** + * Constructeur de la classe Rule. + * @param name Nom de la règle + * @param dependencies Liste des dépendances + * @param commands Liste des commandes + * @param isPhony true si la règle est phony, false sinon + */ public Rule(String name, List<String> dependencies, List<String> commands, boolean isPhony) { this.name = name; this.dependencies = dependencies; @@ -17,12 +39,40 @@ public class Rule { this.isPhony = isPhony; } + /** + * Récupère le nom de la règle. + * @return Le nom de la règle + */ public String getName() { return name; } + + /** + * Récupère la liste des dépendances de la règle. + * @return La liste des dépendances + */ public List<String> getDependencies() { return dependencies; } + + /** + * Récupère la liste des commandes de la règle. + * @return La liste des commandes + */ public List<String> getCommands() { return commands; } + + /** + * Vérifie si la règle est phony. + * @return true si la règle est phony, false sinon. + */ public boolean isPhony() { return isPhony; } + + /** + * Vérifie si la règle est vide (sans dépendances ni commandes). + * @return true si la règle est vide, false sinon + */ public boolean isEmpty() { return dependencies.isEmpty() && commands.isEmpty(); } + /** + * Vérifie si la règle doit être mise à jour. + * @return true si la règle doit être mise à jour, false sinon + */ public boolean needsUpdate() { if (BakeCLI.isDebug()){ System.out.println("Debug : Checking if rule " + name + " needs update"); diff --git a/src/fr/monlouyan/bakefile/TimestampManager.java b/src/fr/monlouyan/bakefile/TimestampManager.java index 6ffac34..76f8cfc 100644 --- a/src/fr/monlouyan/bakefile/TimestampManager.java +++ b/src/fr/monlouyan/bakefile/TimestampManager.java @@ -6,18 +6,23 @@ import java.util.Date; /** * Classe utilitaire pour la gestion des timestamps des fichiers. - * Dernière modification : 04/02/2025 * * @author Moncef STITI, Yanis HAMOUDI * @version 1.0 - * @date 04/02/2025 */ public class TimestampManager { + /** + * Constructeur de TimestampManager. + */ + public TimestampManager() { + // Constructeur inutile + } + /** * Récupère le timestamp d'un fichier. - * @param filePath Le chemin du fichier. * @return Le timestamp du fichier, ou 0 si le fichier n'existe pas. + * @param file Le fichier */ public static long getTimestamp(File file) { if (file.exists()) { diff --git a/src/fr/monlouyan/bakefile/tests/BakeTestRunner.java b/src/fr/monlouyan/bakefile/tests/BakeTestRunner.java deleted file mode 100644 index 2f2cfae..0000000 --- a/src/fr/monlouyan/bakefile/tests/BakeTestRunner.java +++ /dev/null @@ -1,884 +0,0 @@ -package fr.monlouyan.bakefile.tests; - -import javax.swing.*; -import javax.swing.border.EmptyBorder; -import javax.swing.table.DefaultTableModel; -import javax.swing.text.DefaultCaret; -import java.awt.*; -import java.io.*; -import java.nio.file.*; -import java.nio.file.attribute.BasicFileAttributes; -import java.text.SimpleDateFormat; -import java.util.*; -import java.util.List; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -public class BakeTestRunner extends JFrame { - private JPanel mainPanel; - private JTable testTable; - private DefaultTableModel tableModel; - private JTextArea logArea; - private JButton runSelectedButton; - private JButton runAllButton; - private JComboBox<String> languageComboBox; - private JProgressBar progressBar; - private JButton openLogsButton; - private JButton compareSelectedButton; - - private final ExecutorService executor = Executors.newSingleThreadExecutor(); - private final String baseDir = System.getProperty("user.dir"); - private final String logsDir = baseDir + File.separator + "logs"; - private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - private final Color PASSED_COLOR = new Color(220, 255, 220); - private final Color FAILED_COLOR = new Color(255, 220, 220); - private final Color RUNNING_COLOR = new Color(220, 220, 255); - - enum TestStatus { - NOT_RUN, RUNNING, PASSED, FAILED - } - - public BakeTestRunner() { - setTitle("Bake Test Runner"); - setSize(1000, 700); - setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); - setLocationRelativeTo(null); - - setupUI(); - loadTests(); - createLogsDirectory(); - } - - private void setupUI() { - mainPanel = new JPanel(new BorderLayout(10, 10)); - mainPanel.setBorder(new EmptyBorder(10, 10, 10, 10)); - - // Panel du haut avec contrôles modernisés - JPanel controlPanel = new JPanel(); - controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.Y_AXIS)); - - // Première ligne de contrôles - JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 5)); - runSelectedButton = new JButton("Run Selected Tests"); - runAllButton = new JButton("Run All Tests"); - compareSelectedButton = new JButton("Compare Selected Test"); - - // Styliser les boutons - runSelectedButton.setBackground(new Color(255, 255, 255)); // Steel Blue - runSelectedButton.setForeground(Color.BLACK); - runSelectedButton.setFont(new Font("Dialog", Font.BOLD, 12)); - - runAllButton.setBackground(new Color(255, 255, 255)); // Medium Sea Green - runAllButton.setForeground(Color.BLACK); - runAllButton.setFont(new Font("Dialog", Font.BOLD, 12)); - - compareSelectedButton.setBackground(new Color(255, 255, 255)); // Dodger Blue - compareSelectedButton.setForeground(Color.BLACK); - compareSelectedButton.setFont(new Font("Dialog", Font.BOLD, 12)); - - buttonPanel.add(runSelectedButton); - buttonPanel.add(runAllButton); - buttonPanel.add(compareSelectedButton); - - // Deuxième ligne de contrôles - JPanel filterPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 10, 5)); - filterPanel.add(new JLabel("Language:")); - languageComboBox = new JComboBox<>(new String[]{"All", "C", "Java"}); - languageComboBox.setPreferredSize(new Dimension(100, 25)); - - openLogsButton = new JButton("Open Logs Directory"); - openLogsButton.setBackground(new Color(211, 211, 211)); // Light Gray - openLogsButton.setFont(new Font("Dialog", Font.PLAIN, 12)); - - filterPanel.add(languageComboBox); - filterPanel.add(Box.createHorizontalStrut(10)); - filterPanel.add(openLogsButton); - - controlPanel.add(buttonPanel); - controlPanel.add(Box.createVerticalStrut(5)); - controlPanel.add(filterPanel); - - // Tableau stylisé pour la liste des tests - String[] columnNames = {"#", "Language", "Test Name", "Status", "Last Run"}; - tableModel = new DefaultTableModel(columnNames, 0) { - @Override - public boolean isCellEditable(int row, int column) { - return false; - } - - @Override - public Class<?> getColumnClass(int column) { - if (column == 0) return Integer.class; - return String.class; - } - }; - - testTable = new JTable(tableModel) { - @Override - public Component prepareRenderer(javax.swing.table.TableCellRenderer renderer, int row, int column) { - Component comp = super.prepareRenderer(renderer, row, column); - - // Alterner les couleurs des lignes - if (!comp.getBackground().equals(getSelectionBackground())) { - String status = (String) getValueAt(row, 3); - if ("PASSED".equals(status)) { - comp.setBackground(PASSED_COLOR); - } else if ("FAILED".equals(status)) { - comp.setBackground(FAILED_COLOR); - } else if ("RUNNING".equals(status)) { - comp.setBackground(RUNNING_COLOR); - } else { - comp.setBackground(row % 2 == 0 ? new Color(240, 240, 250) : Color.WHITE); - } - } - return comp; - } - }; - - testTable.getColumnModel().getColumn(0).setPreferredWidth(30); - testTable.getColumnModel().getColumn(0).setMaxWidth(50); - testTable.getColumnModel().getColumn(1).setPreferredWidth(70); - testTable.getColumnModel().getColumn(1).setMaxWidth(100); - testTable.getColumnModel().getColumn(2).setPreferredWidth(300); - testTable.getColumnModel().getColumn(3).setPreferredWidth(80); - testTable.getColumnModel().getColumn(3).setMaxWidth(120); - testTable.getColumnModel().getColumn(4).setPreferredWidth(150); - - testTable.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - testTable.setRowHeight(28); - testTable.setShowGrid(false); - testTable.setIntercellSpacing(new Dimension(0, 0)); - testTable.getTableHeader().setFont(new Font("Dialog", Font.BOLD, 12)); - testTable.getTableHeader().setOpaque(false); - testTable.getTableHeader().setBackground(new Color(240, 240, 240)); - testTable.setFont(new Font("Dialog", Font.PLAIN, 12)); - - // Zone de logs améliorée - logArea = new JTextArea(); - logArea.setEditable(false); - logArea.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); - logArea.setBackground(new Color(250, 250, 250)); - logArea.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5)); - DefaultCaret caret = (DefaultCaret) logArea.getCaret(); - caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); - - JPanel logPanel = new JPanel(new BorderLayout()); - logPanel.add(new JLabel(" Test Output:"), BorderLayout.NORTH); - logPanel.add(new JScrollPane(logArea), BorderLayout.CENTER); - - // Barre de progression stylisée - progressBar = new JProgressBar(0, 100); - progressBar.setStringPainted(true); - progressBar.setString("Ready"); - progressBar.setFont(new Font("Dialog", Font.BOLD, 11)); - progressBar.setForeground(new Color(46, 139, 87)); // Sea Green - - // Layout - JSplitPane splitPane = new JSplitPane( - JSplitPane.VERTICAL_SPLIT, - new JScrollPane(testTable), - logPanel - ); - splitPane.setDividerLocation(300); - splitPane.setContinuousLayout(true); - splitPane.setDividerSize(5); - - mainPanel.add(controlPanel, BorderLayout.NORTH); - mainPanel.add(splitPane, BorderLayout.CENTER); - - JPanel statusPanel = new JPanel(new BorderLayout()); - statusPanel.setBorder(BorderFactory.createEmptyBorder(5, 0, 0, 0)); - statusPanel.add(progressBar, BorderLayout.CENTER); - mainPanel.add(statusPanel, BorderLayout.SOUTH); - - setContentPane(mainPanel); - - // Add action listeners - runSelectedButton.addActionListener(e -> runSelectedTests()); - runAllButton.addActionListener(e -> runAllTests()); - compareSelectedButton.addActionListener(e -> compareSelectedTest()); - openLogsButton.addActionListener(e -> openLogsDirectory()); - languageComboBox.addActionListener(e -> filterTestsByLanguage()); - } - - private void createLogsDirectory() { - try { - Files.createDirectories(Paths.get(logsDir)); - } catch (IOException e) { - logMessage("Error creating logs directory: " + e.getMessage()); - } - } - - private void openLogsDirectory() { - try { - Desktop.getDesktop().open(new File(logsDir)); - } catch (IOException e) { - logMessage("Error opening logs directory: " + e.getMessage()); - JOptionPane.showMessageDialog(this, - "Could not open logs directory: " + e.getMessage(), - "Error", JOptionPane.ERROR_MESSAGE); - } - } - - private void compareSelectedTest() { - int selectedRow = testTable.getSelectedRow(); - if (selectedRow == -1) { - JOptionPane.showMessageDialog(this, - "Please select a test to compare", - "No Test Selected", JOptionPane.WARNING_MESSAGE); - return; - } - - String language = (String) tableModel.getValueAt(selectedRow, 1); - String testName = (String) tableModel.getValueAt(selectedRow, 2); - - String logFilePath = logsDir + File.separator + language + "_" + testName + ".log"; - File logFile = new File(logFilePath); - - if (!logFile.exists()) { - JOptionPane.showMessageDialog(this, - "No log file found for this test. Please run the test first.", - "Log Not Found", JOptionPane.WARNING_MESSAGE); - return; - } - - showComparisonDialog(logFile, language, testName); - } - - private void showComparisonDialog(File logFile, String language, String testName) { - JDialog dialog = new JDialog(this, "Comparison: " + language + " - " + testName, true); - dialog.setLayout(new BorderLayout(10, 10)); - dialog.setSize(1000, 600); - dialog.setLocationRelativeTo(this); - - try { - List<String> lines = Files.readAllLines(logFile.toPath()); - String content = String.join("\n", lines); - - // Split content to make and bake sections if possible - String makeOutput = ""; - String bakeOutput = ""; - String comparisonResults = ""; - - // Better parsing - enhanced for clearer splitting - int makeIndex = content.indexOf("=== Make Output ==="); - int bakeIndex = content.indexOf("=== Bake Output ==="); - int comparisonIndex = content.indexOf("=== Comparison Results ==="); - - if (makeIndex != -1 && bakeIndex != -1) { - makeOutput = content.substring(makeIndex, bakeIndex).trim(); - if (comparisonIndex != -1) { - bakeOutput = content.substring(bakeIndex, comparisonIndex).trim(); - comparisonResults = content.substring(comparisonIndex).trim(); - } else { - bakeOutput = content.substring(bakeIndex).trim(); - } - } - - JTextPane makeArea = createStyledTextPane(makeOutput); - JTextPane bakeArea = createStyledTextPane(bakeOutput); - JTextPane comparisonArea = createStyledTextPane( - comparisonIndex != -1 ? comparisonResults : "No comparison results available." - ); - - JTabbedPane tabbedPane = new JTabbedPane(); - tabbedPane.addTab("Make Output", new JScrollPane(makeArea)); - tabbedPane.addTab("Bake Output", new JScrollPane(bakeArea)); - tabbedPane.addTab("Comparison", new JScrollPane(comparisonArea)); - tabbedPane.setFont(new Font("Dialog", Font.PLAIN, 12)); - - // Détection et mise en évidence des différences - highlightDifferences(makeArea, bakeArea); - - dialog.add(tabbedPane, BorderLayout.CENTER); - - JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.RIGHT)); - JButton closeButton = new JButton("Close"); - closeButton.setFont(new Font("Dialog", Font.BOLD, 12)); - closeButton.addActionListener(e -> dialog.dispose()); - buttonPanel.add(closeButton); - - dialog.add(buttonPanel, BorderLayout.SOUTH); - dialog.setVisible(true); - - } catch (IOException e) { - JOptionPane.showMessageDialog(dialog, - "Error reading log file: " + e.getMessage(), - "Error", JOptionPane.ERROR_MESSAGE); - } - } - - // Méthode pour créer un JTextPane stylisé - private JTextPane createStyledTextPane(String content) { - JTextPane textPane = new JTextPane(); - textPane.setEditable(false); - textPane.setFont(new Font(Font.MONOSPACED, Font.PLAIN, 12)); - textPane.setText(content); - textPane.setMargin(new Insets(10, 10, 10, 10)); - textPane.setBackground(new Color(252, 252, 252)); - - // Appliquer une coloration syntaxique basique - highlightKeywords(textPane); - - return textPane; - } - -// Méthode pour mettre en évidence les mots clés -private void highlightKeywords(JTextPane textPane) { - // Obtenez le document et le kit de style - javax.swing.text.StyledDocument doc = textPane.getStyledDocument(); - javax.swing.text.Style defaultStyle = javax.swing.text.StyleContext.getDefaultStyleContext() - .getStyle(javax.swing.text.StyleContext.DEFAULT_STYLE); - - // Créez différents styles pour différents types de texte - javax.swing.text.Style errorStyle = doc.addStyle("errorStyle", defaultStyle); - javax.swing.text.StyleConstants.setForeground(errorStyle, new Color(220, 0, 0)); - javax.swing.text.StyleConstants.setBold(errorStyle, true); - - javax.swing.text.Style successStyle = doc.addStyle("successStyle", defaultStyle); - javax.swing.text.StyleConstants.setForeground(successStyle, new Color(0, 128, 0)); - javax.swing.text.StyleConstants.setBold(successStyle, true); - - // Appliquez les styles aux mots-clés - String text = textPane.getText(); - applyStyle(doc, text, "Error", errorStyle); - applyStyle(doc, text, "error", errorStyle); - applyStyle(doc, text, "ERROR", errorStyle); - applyStyle(doc, text, "failed", errorStyle); - applyStyle(doc, text, "FAILED", errorStyle); - - applyStyle(doc, text, "PASSED", successStyle); - applyStyle(doc, text, "passed", successStyle); - applyStyle(doc, text, "successful", successStyle); -} - -// Méthode auxiliaire pour appliquer un style à un mot-clé spécifique -private void applyStyle(javax.swing.text.StyledDocument doc, String text, String pattern, javax.swing.text.Style style) { - int pos = 0; - while ((pos = text.indexOf(pattern, pos)) >= 0) { - doc.setCharacterAttributes(pos, pattern.length(), style, true); - pos += pattern.length(); - } -} - -// Méthode pour mettre en évidence les différences entre les sorties make et bake -private void highlightDifferences(JTextPane makeArea, JTextPane bakeArea) { - // Cette méthode devrait idéalement comparer les lignes et mettre en évidence les différences - // Pour une première implémentation simple, nous allons juste colorer les lignes contenant des erreurs - - highlightKeywords(makeArea); - highlightKeywords(bakeArea); -} - - private void loadTests() { - tableModel.setRowCount(0); - - // Load C tests - loadTestsForLanguage("C"); - - // Load Java tests - loadTestsForLanguage("Java"); - } - - private void loadTestsForLanguage(String language) { - File languageDir = new File(baseDir + File.separator + "tests" + File.separator + language); - if (!languageDir.exists() || !languageDir.isDirectory()) { - logMessage("Warning: Directory not found: " + languageDir.getPath()); - return; - } - - File[] testDirs = languageDir.listFiles(File::isDirectory); - if (testDirs == null) { - logMessage("Warning: No test directories found in " + languageDir.getPath()); - return; - } - - Arrays.sort(testDirs, (a, b) -> { - // Extract test number for sorting - Pattern pattern = Pattern.compile("test-(\\d+)"); - Matcher matcherA = pattern.matcher(a.getName()); - Matcher matcherB = pattern.matcher(b.getName()); - - if (matcherA.find() && matcherB.find()) { - try { - int numA = Integer.parseInt(matcherA.group(1)); - int numB = Integer.parseInt(matcherB.group(1)); - return Integer.compare(numA, numB); - } catch (NumberFormatException e) { - return a.getName().compareTo(b.getName()); - } - } - return a.getName().compareTo(b.getName()); - }); - - for (File testDir : testDirs) { - String testName = testDir.getName(); - if (testName.startsWith("test-")) { - Object[] row = {tableModel.getRowCount() + 1, language, testName, "Not Run", ""}; - tableModel.addRow(row); - } - } - } - - private void filterTestsByLanguage() { - String selectedLanguage = (String) languageComboBox.getSelectedItem(); - if (selectedLanguage == null || selectedLanguage.equals("All")) { - loadTests(); - return; - } - - tableModel.setRowCount(0); - loadTestsForLanguage(selectedLanguage); - } - - private void runSelectedTests() { - int[] selectedRows = testTable.getSelectedRows(); - if (selectedRows.length == 0) { - JOptionPane.showMessageDialog(this, - "Please select at least one test to run", - "No Test Selected", JOptionPane.WARNING_MESSAGE); - return; - } - - List<TestInfo> testsToRun = new ArrayList<>(); - for (int row : selectedRows) { - String language = (String) tableModel.getValueAt(row, 1); - String testName = (String) tableModel.getValueAt(row, 2); - testsToRun.add(new TestInfo(row, language, testName)); - } - - disableButtons(); - runTests(testsToRun); - } - - private void runAllTests() { - List<TestInfo> testsToRun = new ArrayList<>(); - for (int row = 0; row < tableModel.getRowCount(); row++) { - String language = (String) tableModel.getValueAt(row, 1); - String testName = (String) tableModel.getValueAt(row, 2); - testsToRun.add(new TestInfo(row, language, testName)); - } - - disableButtons(); - runTests(testsToRun); - } - - private void disableButtons() { - runSelectedButton.setEnabled(false); - runAllButton.setEnabled(false); - compareSelectedButton.setEnabled(false); - languageComboBox.setEnabled(false); - } - - private void enableButtons() { - runSelectedButton.setEnabled(true); - runAllButton.setEnabled(true); - compareSelectedButton.setEnabled(true); - languageComboBox.setEnabled(true); - } - - private void runTests(List<TestInfo> testsToRun) { - progressBar.setValue(0); - progressBar.setString("Running tests (0/" + testsToRun.size() + ")"); - logArea.setText(""); - - executor.submit(() -> { - try { - int total = testsToRun.size(); - int current = 0; - - for (int i = 0; i < testsToRun.size(); i++) { - TestInfo test = testsToRun.get(i); - final int currentTest = i + 1; - - // Update UI to show we're running this test - SwingUtilities.invokeLater(() -> { - tableModel.setValueAt(TestStatus.RUNNING.name(), test.row, 3); - testTable.setValueAt(TestStatus.RUNNING.name(), test.row, 3); - testTable.setValueAt(dateFormat.format(new Date()), test.row, 4); - - // Highlight the row - testTable.setRowSelectionInterval(test.row, test.row); - - // Update the progress bar - progressBar.setValue((int)((double)currentTest / total * 100)); - progressBar.setString("Running tests (" + currentTest + "/" + total + ")"); - }); - - // Run the test - logMessage("\n========================================================"); - logMessage("Running Test: " + test.language + " - " + test.testName); - logMessage("========================================================"); - - boolean success = runTest(test); - - // Update UI with the result - SwingUtilities.invokeLater(() -> { - testTable.setValueAt(success ? TestStatus.PASSED.name() : TestStatus.FAILED.name(), - test.row, 3); - }); - } - - // Test run complete - SwingUtilities.invokeLater(() -> { - progressBar.setValue(100); - progressBar.setString("All tests completed"); - enableButtons(); - logMessage("\n========================================================"); - logMessage("Test run completed at " + dateFormat.format(new Date())); - logMessage("========================================================"); - }); - } catch (Exception e) { - SwingUtilities.invokeLater(() -> { - logMessage("Error running tests: " + e.getMessage()); - for (StackTraceElement element : e.getStackTrace()) { - logMessage(" " + element.toString()); - } - progressBar.setString("Error running tests"); - enableButtons(); - }); - } - }); - } - - private boolean runTest(TestInfo test) { - String testDir = baseDir + File.separator + "tests" + File.separator + - test.language + File.separator + test.testName; - - File makeDir = new File(testDir + File.separator + "make"); - File bakeDir = new File(testDir + File.separator + "bake"); - - if (!makeDir.exists() || !bakeDir.exists()) { - logMessage("Error: Make or Bake directory not found for test " + test.testName); - return false; - } - - String logFilePath = logsDir + File.separator + test.language + "_" + test.testName + ".log"; - - try (PrintWriter writer = new PrintWriter(new FileWriter(logFilePath))) { - // Header information - writer.println("Test: " + test.language + " - " + test.testName); - writer.println("Date: " + dateFormat.format(new Date())); - writer.println("========================================================"); - - // Compare initial file state - writer.println("\n=== Initial File Comparison ==="); - logMessage("Comparing initial files..."); - - Map<String, FileInfo> makeFiles = scanDirectory(makeDir); - Map<String, FileInfo> bakeFiles = scanDirectory(bakeDir); - - compareAndLogFiles(makeFiles, bakeFiles, writer); - - // Run make - writer.println("\n=== Make Output ==="); - logMessage("Running make..."); - - ProcessResult makeResult = runProcess("make", makeDir); - writer.println(makeResult.output); - logMessage(makeResult.output); - - // Run bake - writer.println("\n=== Bake Output ==="); - logMessage("Running bake..."); - - ProcessResult bakeResult = runProcess("java -cp bakefile.jar fr.monlouyan.bakefile.Main", bakeDir); - writer.println(bakeResult.output); - logMessage(bakeResult.output); - - // Compare results - logMessage("Comparing results..."); - writer.println("\n=== Comparison Results ==="); - - // Compare exit codes - boolean exitCodesMatch = makeResult.exitCode == bakeResult.exitCode; - writer.println("Exit codes match: " + exitCodesMatch); - writer.println("Make exit code: " + makeResult.exitCode); - writer.println("Bake exit code: " + bakeResult.exitCode); - - // Compare output patterns (ignoring the tool name differences) - boolean outputPatternsMatch = compareOutputPatterns(makeResult.output, bakeResult.output); - writer.println("Output patterns match: " + outputPatternsMatch); - - // Compare final file state - writer.println("\n=== Final File State Comparison ==="); - - Map<String, FileInfo> makeFinalFiles = scanDirectory(makeDir); - Map<String, FileInfo> bakeFinalFiles = scanDirectory(bakeDir); - - compareAndLogFiles(makeFinalFiles, bakeFinalFiles, writer); - - // Check if files were created or modified as expected - boolean fileChangesMatch = compareFileChanges(makeFiles, makeFinalFiles, bakeFiles, bakeFinalFiles); - writer.println("File changes match: " + fileChangesMatch); - - // Test summary - boolean testPassed = exitCodesMatch && outputPatternsMatch && fileChangesMatch; - writer.println("\n=== Test Result ==="); - writer.println(testPassed ? "PASSED" : "FAILED"); - - logMessage(testPassed ? "Test PASSED" : "Test FAILED"); - - return testPassed; - - } catch (IOException e) { - logMessage("Error running test: " + e.getMessage()); - return false; - } - } - - private boolean compareFileChanges( - Map<String, FileInfo> makeInitial, - Map<String, FileInfo> makeFinal, - Map<String, FileInfo> bakeInitial, - Map<String, FileInfo> bakeFinal) { - - // Check if the same files were created in both directories - Set<String> makeCreated = new HashSet<>(makeFinal.keySet()); - makeCreated.removeAll(makeInitial.keySet()); - - Set<String> bakeCreated = new HashSet<>(bakeFinal.keySet()); - bakeCreated.removeAll(bakeInitial.keySet()); - - if (!makeCreated.equals(bakeCreated)) { - logMessage("Different files created:\nMake: " + makeCreated + "\nBake: " + bakeCreated); - return false; - } - - // Check if the same files were modified - boolean filesMatch = true; - for (String file : makeInitial.keySet()) { - if (makeFinal.containsKey(file) && bakeInitial.containsKey(file) && bakeFinal.containsKey(file)) { - boolean makeModified = !makeInitial.get(file).equals(makeFinal.get(file)); - boolean bakeModified = !bakeInitial.get(file).equals(bakeFinal.get(file)); - - if (makeModified != bakeModified) { - logMessage("File modification mismatch for " + file + - "\nMake modified: " + makeModified + - "\nBake modified: " + bakeModified); - filesMatch = false; - } - } - } - - return filesMatch; - } - - private ProcessResult runProcess(String command, File directory) throws IOException { - ProcessBuilder processBuilder = new ProcessBuilder("/bin/sh", "-c", command); - processBuilder.directory(directory); - Process process = processBuilder.start(); - - // Capture stdout and stderr - StringBuilder output = new StringBuilder(); - try (BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream())); - BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { - - String line; - while ((line = stdInput.readLine()) != null) { - output.append(line).append("\n"); - } - - while ((line = stdError.readLine()) != null) { - output.append("ERROR: ").append(line).append("\n"); - } - } - - try { - process.waitFor(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - - return new ProcessResult(process.exitValue(), output.toString()); - } - - private Map<String, FileInfo> scanDirectory(File directory) throws IOException { - Map<String, FileInfo> files = new HashMap<>(); - - if (!directory.exists() || !directory.isDirectory()) { - return files; - } - - Files.walkFileTree(directory.toPath(), new SimpleFileVisitor<Path>() { - @Override - public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { - String relativePath = directory.toPath().relativize(file).toString(); - - // Skip bakefile.jar to avoid differences - if (relativePath.equals("bakefile.jar")) { - return FileVisitResult.CONTINUE; - } - - FileInfo info = new FileInfo( - Files.isRegularFile(file), - attrs.size(), - attrs.lastModifiedTime().toMillis() - ); - - files.put(relativePath, info); - return FileVisitResult.CONTINUE; - } - }); - - return files; - } - - private void compareAndLogFiles(Map<String, FileInfo> makeFiles, - Map<String, FileInfo> bakeFiles, - PrintWriter writer) { - Set<String> allFiles = new HashSet<>(); - allFiles.addAll(makeFiles.keySet()); - allFiles.addAll(bakeFiles.keySet()); - - List<String> sortedFiles = new ArrayList<>(allFiles); - Collections.sort(sortedFiles); - - writer.println("File comparison:"); - for (String file : sortedFiles) { - FileInfo makeInfo = makeFiles.get(file); - FileInfo bakeInfo = bakeFiles.get(file); - - writer.print(file + ": "); - if (makeInfo == null) { - writer.println("Only in Bake"); - } else if (bakeInfo == null) { - writer.println("Only in Make"); - } else if (makeInfo.equals(bakeInfo)) { - writer.println("Identical"); - } else { - writer.println("Different"); - writer.println(" Make: " + makeInfo); - writer.println(" Bake: " + bakeInfo); - } - } - } - - private boolean compareOutputPatterns(String makeOutput, String bakeOutput) { - // Normalize output by replacing tool-specific words - String normalizedMake = makeOutput.replaceAll("\\bmake\\b", "TOOL") - .replaceAll("\\bMake\\b", "TOOL"); - String normalizedBake = bakeOutput.replaceAll("\\bbake\\b", "TOOL") - .replaceAll("\\bBake\\b", "TOOL"); - - // Compare line by line, ignoring exact timestamps or specific paths - String[] makeLines = normalizedMake.split("\n"); - String[] bakeLines = normalizedBake.split("\n"); - - // If line counts are very different, they're probably not matching - if (Math.abs(makeLines.length - bakeLines.length) > 2) { - logMessage("Output line count mismatch: Make=" + makeLines.length + - ", Bake=" + bakeLines.length); - return false; - } - - // Compare key patterns like error messages, file operations, etc. - Pattern errorPattern = Pattern.compile(".*Error.*|.*\\*\\*\\*.*|.*failed.*", - Pattern.CASE_INSENSITIVE); - Pattern commandPattern = Pattern.compile("^[a-z0-9_\\-]+ .*|^\\$.*"); - - List<String> makeErrors = extractMatches(makeLines, errorPattern); - List<String> bakeErrors = extractMatches(bakeLines, errorPattern); - - List<String> makeCommands = extractMatches(makeLines, commandPattern); - List<String> bakeCommands = extractMatches(bakeLines, commandPattern); - - // If error counts are different, that's a significant difference - if (makeErrors.size() != bakeErrors.size()) { - logMessage("Error count mismatch: Make=" + makeErrors.size() + - ", Bake=" + bakeErrors.size()); - return false; - } - - // If command counts are different, that's a significant difference - if (makeCommands.size() != bakeCommands.size()) { - logMessage("Command count mismatch: Make=" + makeCommands.size() + - ", Bake=" + bakeCommands.size()); - return false; - } - - return true; - } - - private List<String> extractMatches(String[] lines, Pattern pattern) { - return Arrays.stream(lines) - .filter(line -> pattern.matcher(line).matches()) - .collect(Collectors.toList()); - } - - private void logMessage(String message) { - SwingUtilities.invokeLater(() -> { - logArea.append(message + "\n"); - // Scroll to bottom - logArea.setCaretPosition(logArea.getDocument().getLength()); - }); - } - - private static class TestInfo { - int row; - String language; - String testName; - - TestInfo(int row, String language, String testName) { - this.row = row; - this.language = language; - this.testName = testName; - } - } - - private static class FileInfo { - boolean isFile; - long size; - long lastModified; - - FileInfo(boolean isFile, long size, long lastModified) { - this.isFile = isFile; - this.size = size; - this.lastModified = lastModified; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof FileInfo)) { - return false; - } - FileInfo other = (FileInfo) obj; - return isFile == other.isFile && size == other.size; - // We don't compare lastModified times directly - } - - @Override - public String toString() { - return "isFile=" + isFile + ", size=" + size + ", lastModified=" + - new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date(lastModified)); - } - } - - private static class ProcessResult { - int exitCode; - String output; - - ProcessResult(int exitCode, String output) { - this.exitCode = exitCode; - this.output = output; - } - } - - public static void main(String[] args) { - SwingUtilities.invokeLater(() -> { - try { - // Set native look and feel - UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } catch (Exception e) { - e.printStackTrace(); - } - - BakeTestRunner runner = new BakeTestRunner(); - runner.setVisible(true); - }); - } -} \ No newline at end of file diff --git a/tests/bakefile.jar b/tests/bakefile.jar deleted file mode 100644 index 875b1b3..0000000 Binary files a/tests/bakefile.jar and /dev/null differ