Commit e149523c authored by Martti Käärik's avatar Martti Käärik
Browse files

EnumTypingTest with implicitly typed enumeration values + test helpers:...

EnumTypingTest with implicitly typed enumeration values + test helpers: multi-file fixture F1, fixture loader, link oracle
parent 89d67951
Loading
Loading
Loading
Loading
+21 −0
Changes for plugins/org.etsi.mts.tdl.tx.tests/src/fixtures/f1/Config.tdltx: 21 added lines, 0 removed lines.
Original line number Diff line number Diff line
// F1 multi-file fixture: gate/component/configuration definitions. Imports
// F1Data (types used by the gate and the variables) and is imported by TD.tdltx.
Package F1Config {
    Import all from TDL
    Import Shape, Point, Colour from F1Data   // explicit import (TD.tdltx uses 'Import all')

    Message Gate shapeGate accepts Shape, Point, Colour, Integer

    Component Node {
        gate shapeGate g
        variable Integer count
        variable Shape lastShape
        timer t
    }

    Configuration Basic {
        Node tester as Tester,
        Node sut as SUT,
        connect tester::g to sut::g
    }
}
+39 −0
Changes for plugins/org.etsi.mts.tdl.tx.tests/src/fixtures/f1/Data.tdltx: 39 added lines, 0 removed lines.
Original line number Diff line number Diff line
// F1 multi-file fixture (docs/reliability/11-test-suite-design.md): data
// definitions imported by Config.tdltx and TD.tdltx. Every kind of element
// that the other files reference across the file boundary lives here.
Package F1Data {
    Import all from TDL

    Enumerated Colour {
        Colour red,     // explicit type
        green,          // implicit: typed by the enclosing enumeration
        blue
    }

    Type Id
    Id id1
    Id id2

    Structure Point (
        Integer x,
        Integer y
    )
    Collection Points of Point

    Structure Shape (
        Id id,
        Colour colour,
        Points outline
    )

    Point origin ( x = 0, y = 0 )
    Point unit ( x = 1, y = 1 )
    Points square [ origin, unit ]
    Shape shape1 ( id = id1, colour = red, outline = square )
    Shape shape2 ( id = id2, colour = green, outline = square )

    second oneSecond
    second twoSeconds

    Function area ( Shape s ) returns Integer : "area of a shape"
}
+30 −0
Changes for plugins/org.etsi.mts.tdl.tx.tests/src/fixtures/f1/TD.tdltx: 30 added lines, 0 removed lines.
Original line number Diff line number Diff line
// F1 multi-file fixture: test descriptions. References data (F1Data) and the
// configuration, component instances, gates and timers (F1Config) across two
// file boundaries; also references an element (Basic) that itself references
// a third file (Node -> shapeGate -> Shape).
Package F1TD {
    Import all from TDL
    Import all from F1Data
    Import all from F1Config

    Test Description sendShape uses Basic {
        tester::g sends shape1 to sut::g
        sut::g sends shape2 ( colour = blue ) to tester::g
        tester::g receives shape1 ( colour = ?, outline = square ) from sut::g
        set verdict to pass
    }

    Test Description withTimers uses Basic {
        start tester::t for oneSecond
        tester::g sends origin to sut::g
        alternatively {
            tester::g receives unit from sut::g
            stop tester::t
            set verdict to pass
        } or {
            timeout on tester::t
            set verdict to fail
        }
        wait for twoSeconds on tester
    }
}
+97 −0
Changes for plugins/org.etsi.mts.tdl.tx.tests/src/org/etsi/mts/tdl/tx/tests/EnumTypingTest.java: 97 added lines, 0 removed lines.
Original line number Diff line number Diff line
package org.etsi.mts.tdl.tx.tests;

import java.io.File;
import java.util.List;

import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.serializer.ISerializer;
import org.eclipse.xtext.validation.Issue;
import org.etsi.mts.tdl.EnumDataType;
import org.etsi.mts.tdl.Package;
import org.etsi.mts.tdl.SimpleDataInstance;
import org.etsi.mts.tdl.tdlPackage;
import org.etsi.mts.tdl.tx.tests.oracle.LinkOracle;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import com.google.inject.Inject;

/**
 * Task 2.2 / experiment X5: enumeration values are typed by their enclosing
 * enumeration without derived state — on read, for EMF multiplicity checks,
 * for cross-file references, and without leaking into the serialised text.
 */
public class EnumTypingTest extends TDLtxAbstractTest {

	@Inject
	ISerializer serializer;

	@Override
	public void verifySingleModel(File file) {
	}

	private EnumDataType colour() throws Exception {
		Fixtures.loadStandardLibrary(parseHelper, rs);
		XtextResource data = Fixtures.load(parseHelper, rs, "f1", Fixtures.F1).get(0);
		Package pkg = (Package) data.getContents().get(0);
		return (EnumDataType) pkg.getPackagedElement().stream().filter(e -> "Colour".equals(e.getName())).findFirst().orElseThrow();
	}

	@Test
	public void implicitValuesAreTypedByTheEnumeration() throws Exception {
		EnumDataType colour = colour();
		Assertions.assertEquals(3, colour.getValue().size());
		for (SimpleDataInstance v : colour.getValue()) {
			Assertions.assertSame(colour, v.getDataType(), v.getName() + ".getDataType()");
			Assertions.assertSame(colour, v.eGet(tdlPackage.Literals.DATA_INSTANCE__DATA_TYPE, false), v.getName() + " eGet(resolve=false)");
			Assertions.assertTrue(v.eIsSet(tdlPackage.Literals.DATA_INSTANCE__DATA_TYPE), v.getName() + " eIsSet");
		}
	}

	@Test
	public void noMultiplicityOrTypeIssuesOnEnumValues() throws Exception {
		EnumDataType colour = colour();
		List<Issue> issues = validationHelper.validate(colour.eResource());
		for (Issue i : issues) {
			boolean aboutValue = i.getUriToProblem() != null && i.getUriToProblem().fragment() != null && colour.getValue().stream()
					.anyMatch(v -> i.getUriToProblem().fragment().equals(v.eResource().getURIFragment(v)));
			Assertions.assertFalse(aboutValue, "issue on an enum value: " + i);
		}
	}

	@Test
	public void implicitTypeIsNotSerialised() throws Exception {
		EnumDataType colour = colour();
		String text = serializer.serialize(colour);
		Assertions.assertTrue(text.contains("Colour red"), "explicit type kept: " + text);
		Assertions.assertFalse(text.contains("Colour green") || text.contains("Colour blue"), "implicit types must stay implicit: " + text);
	}

	@Test
	public void crossFileReferenceToAnImplicitValueResolves() throws Exception {
		Fixtures.loadStandardLibrary(parseHelper, rs);
		List<XtextResource> resources = Fixtures.load(parseHelper, rs, "f1", Fixtures.F1);
		XtextResource td = resources.get(2);
		assertFullyLinked(resources);
		boolean found = LinkOracle.links(td).stream().anyMatch(l -> "blue".equals(l.text) && l.isResolved()
				&& l.target instanceof SimpleDataInstance && ((SimpleDataInstance) l.target).getDataType() instanceof EnumDataType);
		Assertions.assertTrue(found, "TD.tdltx must reference the implicitly typed value 'blue'");
		for (Resource r : resources) {
			Assertions.assertTrue(r.getErrors().isEmpty(), r.getURI().lastSegment() + ": " + r.getErrors());
		}
	}

	private static void assertFullyLinked(List<XtextResource> resources) {
		List<String> problems = new java.util.ArrayList<>();
		for (XtextResource r : resources) {
			for (LinkOracle.Link l : LinkOracle.unresolved(r)) {
				problems.add(r.getURI().lastSegment() + ": " + l);
			}
			for (Resource.Diagnostic d : r.getErrors()) {
				problems.add(r.getURI().lastSegment() + ": error " + d.getMessage() + " (line " + d.getLine() + ")");
			}
		}
		Assertions.assertTrue(problems.isEmpty(), "unresolved references / errors:\n  " + String.join("\n  ", problems));
	}
}
+95 −0
Changes for plugins/org.etsi.mts.tdl.tx.tests/src/org/etsi/mts/tdl/tx/tests/Fixtures.java: 95 added lines, 0 removed lines.
Original line number Diff line number Diff line
package org.etsi.mts.tdl.tx.tests;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.testing.util.ParseHelper;
import org.etsi.mts.tdl.Package;

/**
 * Locates the fixture modules of docs/reliability/11-test-suite-design.md
 * ({@code src/fixtures/<module>}) and loads them into a resource set in a
 * chosen order. F1 is the well-formed multi-file project; broken variants are
 * derived from it by the tests themselves.
 */
public final class Fixtures {

	/** The standard library file every project ships with (the project wizard copies it). */
	public static final String STANDARD_LIBRARY = "TDL.tdltx";

	/** F1 file names in dependency order (Data has no project imports, TD imports both others). */
	public static final List<String> F1 = Arrays.asList("Data.tdltx", "Config.tdltx", "TD.tdltx");

	private Fixtures() {
	}

	public static File libraryFolder() {
		String userDir = System.getProperty("user.dir");
		File f = new File(userDir, "../org.etsi.mts.tdl.library/src/org/etsi/mts/tdl/library");
		if (!f.exists()) {
			f = new File(userDir, "plugins/org.etsi.mts.tdl.library/src/org/etsi/mts/tdl/library");
		}
		if (!f.exists()) {
			throw new IllegalStateException("TDL library folder not found (user.dir=" + userDir + ")");
		}
		return f;
	}

	public static String standardLibrary() throws IOException {
		return new String(Files.readAllBytes(new File(libraryFolder(), STANDARD_LIBRARY).toPath()), StandardCharsets.UTF_8);
	}

	/** Parses the standard library into the resource set (without linking). */
	public static XtextResource loadStandardLibrary(ParseHelper<Package> parseHelper, ResourceSet rs) throws Exception {
		URI uri = URI.createFileURI(new File(libraryFolder(), STANDARD_LIBRARY).getAbsolutePath());
		Package pkg = parseHelper.parse(standardLibrary(), uri, rs);
		return (XtextResource) pkg.eResource();
	}

	public static File folder(String module) {
		String userDir = System.getProperty("user.dir");
		File f = new File(userDir, "src/fixtures/" + module);
		if (!f.exists()) {
			f = new File(userDir, "plugins/org.etsi.mts.tdl.tx.tests/src/fixtures/" + module);
		}
		if (!f.exists()) { // from a sibling test plugin (org.etsi.mts.tdl.tx.ui.tests)
			f = new File(userDir, "../org.etsi.mts.tdl.tx.tests/src/fixtures/" + module);
		}
		if (!f.exists()) {
			throw new IllegalStateException("fixture module not found: " + module + " (user.dir=" + userDir + ")");
		}
		return f;
	}

	public static File file(String module, String name) {
		return new File(folder(module), name);
	}

	public static String content(String module, String name) throws IOException {
		return new String(Files.readAllBytes(file(module, name).toPath()), StandardCharsets.UTF_8);
	}

	/** Parses the given files of a module into the resource set, in the given order, without linking. */
	public static List<XtextResource> load(ParseHelper<Package> parseHelper, ResourceSet rs, String module, List<String> names) throws Exception {
		List<XtextResource> result = new ArrayList<>();
		for (String name : names) {
			result.add(load(parseHelper, rs, module, name, content(module, name)));
		}
		return result;
	}

	/** Parses {@code text} as if it were the given fixture file (used for broken/modified variants). */
	public static XtextResource load(ParseHelper<Package> parseHelper, ResourceSet rs, String module, String name, String text) throws Exception {
		URI uri = URI.createFileURI(file(module, name).getAbsolutePath());
		Package pkg = parseHelper.parse(text, uri, rs);
		return (XtextResource) pkg.eResource();
	}
}
Loading