Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds a test that does load after save and save after load #1655

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 58 additions & 1 deletion megameklab/src/megameklab/util/UnitUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@
package megameklab.util;

import java.awt.Font;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
Expand All @@ -32,12 +35,16 @@
import javax.swing.JFrame;
import javax.swing.JOptionPane;

import megamek.client.ui.enums.ValidationState;
import megamek.common.*;
import megamek.common.annotations.Nullable;
import megamek.common.equipment.AmmoMounted;
import megamek.common.equipment.ArmorType;
import megamek.common.equipment.MiscMounted;
import megamek.common.equipment.WeaponMounted;
import megamek.common.loaders.BLKFile;
import megamek.common.loaders.EntityLoadingException;
import megamek.common.loaders.EntitySavingException;
import megamek.common.verifier.*;
import megamek.common.verifier.TestEntity.Ceil;
import megamek.common.weapons.AmmoWeapon;
Expand Down Expand Up @@ -71,6 +78,17 @@ public class UnitUtil {
private static Font rsFont = null;
private static Font rsBoldFont = null;

public enum UnitValidation {
VALID,
INVALID;

public static UnitValidation of(boolean valid) {
return valid ? VALID : INVALID;
}
}

public record Validation(UnitValidation state, String report) { }

/**
* tells is EquipementType is an equipment that uses crits/mounted and is
* spread across multiple locations
Expand Down Expand Up @@ -1689,9 +1707,11 @@ public static TestEntity getEntityVerifier(Entity unit) {
/**
* check that the unit is vaild
*
* @deprecated prefer to use the new <strong>verify</strong> method which uses the validation record
* @param unit The entity
* @return
* @return report on the errors
*/
@Deprecated
public static String validateUnit(Entity unit) {
StringBuffer sb = new StringBuffer();
TestEntity testEntity = getEntityVerifier(unit);
Expand All @@ -1703,6 +1723,28 @@ public static String validateUnit(Entity unit) {
return sb.toString();
}


/**
* verify
* <p>
* Checks if the unit is valid, returns a record with a state enum saying if it is valid or not, and a report if it failed.
* </p>
*
* @param unit Entity
* @return returns a Validation record with the results of the verification, being an enum saying if it succeeded or failed and the
* report if needed
*/
public static Validation verify(Entity unit) {
StringBuffer sb = new StringBuffer();
TestEntity testEntity = getEntityVerifier(unit);

var succeeded = testEntity.correctEntity(sb, unit.getTechLevel());

var validation = new Validation(UnitValidation.of(succeeded), sb.toString());

return validation;
}

public static void removeAllMiscMounteds(Entity unit, BigInteger flag) {
for (int pos = unit.getEquipment().size() - 1; pos >= 0; pos--) {
Mounted<?> mount = unit.getEquipment().get(pos);
Expand Down Expand Up @@ -2140,4 +2182,19 @@ static boolean isNonMekOrTankWeapon(Entity unit, WeaponType weapon) {
}
return false;
}

public static boolean persistUnit(File outFile, Entity entity) throws EntitySavingException {
if (entity instanceof Mek) {
try (BufferedWriter out = new BufferedWriter(new FileWriter(outFile))) {
out.write(((Mek) entity).getMtf());
} catch (Exception e) {
System.out.println(e.getMessage());
return false;
}
return true;
}

BLKFile.encode(outFile, entity);
return true;
}
}
109 changes: 109 additions & 0 deletions megameklab/unittests/megameklab/loaders/BLKFileTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/*
* Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
*
* This file is part of MegaMek.
*
* MegaMek is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* MegaMek is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with MegaMek. If not, see <http://www.gnu.org/licenses/>.
*/
package megameklab.loaders;

import megamek.common.*;
import megamek.common.equipment.ArmorType;
import megamek.common.loaders.EntitySavingException;
import megameklab.util.UnitUtil;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.UUID;
import java.util.stream.Stream;

import static org.junit.jupiter.api.Assertions.*;

class BLKFileTest {

@ParameterizedTest(name = "{0}")
@MethodSource("allMekfiles")
void loadVerifyUnit(File unitFIle) {
Entity entity = loadUnit(unitFIle);
var validation = UnitUtil.verify(entity);
assertEquals(validation.state(), UnitUtil.UnitValidation.VALID,
"The unit is invalid:\n\t" + entity.getDisplayName() + "\n" + validation.report());
}

// Total of 169 failed entities
// from 4990 entities
@ParameterizedTest(name = "{0}")
@MethodSource("allMekfiles")
void loadVerifySaveVerify(File file) throws EntitySavingException, IOException {
Entity entity = loadUnit(file);
var validation = UnitUtil.verify(entity);

assertEquals(UnitUtil.UnitValidation.VALID, validation.state(),
"The unit is invalid:\n\t" + entity.getDisplayName() + "\n" + validation.report());
File tmpFile;
if (entity instanceof Mek) {
tmpFile = File.createTempFile("tmp_mekfiles/" + UUID.randomUUID() + "/" + file.getName(), ".mtf");

Check warning

Code scanning / CodeQL

Local information disclosure in a temporary directory Medium

Local information disclosure vulnerability due to use of file readable by other local users.
} else {
tmpFile = File.createTempFile("tmp_mekfiles/" + UUID.randomUUID() + "/" + file.getName(), ".blk");

Check warning

Code scanning / CodeQL

Local information disclosure in a temporary directory Medium

Local information disclosure vulnerability due to use of file readable by other local users.
}

if (UnitUtil.persistUnit(tmpFile, entity)) {
Entity repersistedEntity = loadUnit(tmpFile);
var reValidation = UnitUtil.verify(repersistedEntity);
assertEquals(UnitUtil.UnitValidation.VALID, reValidation.state(),
"The unit is invalid after repersisting:\n\t" + tmpFile + "\n\t" + entity.getDisplayName() + "\n" + reValidation.report());
assertEquals(reValidation.state(), validation.state());
}
}

public static List<File> allMekfiles() {
initializeStuff();
try (Stream<Path> paths = Files.walk(Paths.get("data/mekfiles"))) {
return paths
.filter(Files::isRegularFile)
.filter(path -> path.toString().endsWith(".blk"))
.map(Path::toFile)
.toList();
} catch (IOException e) {
// do nothing
}
return List.of();
}

private static void initializeStuff() {
EquipmentType.initializeTypes();
AmmoType.initializeTypes();
ArmorType.initializeTypes();
WeaponType.initializeTypes();
MiscType.initializeTypes();
BombType.initializeTypes();
}

private Entity loadUnit(File file) {
try {
MekFileParser mfp = new MekFileParser(file);
return mfp.getEntity();
} catch (Exception ex) {
fail(ex.getMessage());
}
throw new IllegalStateException("Should not reach here");
}

}
Loading