diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7b24f244..491f54ad 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,12 +5,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout source code - uses: actions/checkout@v1 - with: - ref: master + uses: actions/checkout@v2 - name: Set up JDK 11 uses: actions/setup-java@v1 with: java-version: 11 - name: Build with Gradle run: ./gradlew test checkstyleMain + - name: Checkout source code + uses: codecov/codecov-action@v1 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 1d00785f..00000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -sudo: required -language: java -jdk: - - openjdk11 -before_install: - - chmod +x gradlew -services: - - docker -cache: - directories: - - .autoconf - - $HOME/.m2 - - docker -notifications: - email: - on_success: always - on_failure: always - recipients: - - bren@juanantonio.info -script: - - ./gradlew test checkstyleMain -after_success: - - bash <(curl -s https://codecov.io/bash) diff --git a/README.md b/README.md index ddc1b45b..4706e257 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,7 @@ & the [LeJOS](http://www.lejos.org/) way. [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](/LICENSE) -[![Travis CI](https://travis-ci.org/ev3dev-lang-java/ev3dev-lang-java.svg?branch=develop)](https://travis-ci.org/ev3dev-lang-java/ev3dev-lang-java) - +![Java CI](https://github.com/ev3dev-lang-java/ev3dev-lang-java/workflows/Java%20CI/badge.svg) ![ScreenShot](https://raw.githubusercontent.com/jabrena/ev3dev-lang-java/master/docs/images/theThreeAmigos.jpg) # How to test? diff --git a/config/checkstyle/suppressions.xml b/config/checkstyle/suppressions.xml index f9638ed9..fc81d1fd 100644 --- a/config/checkstyle/suppressions.xml +++ b/config/checkstyle/suppressions.xml @@ -48,6 +48,7 @@ |.*/Sound.java| |.*/EV3Led.java| |.*/Battery.java| + |.*/BatteryOld.java| |.*/NativeFramebuffer.java" /> Only EV3Bricks are supported. */ -public class EV3Led extends EV3DevDevice implements LED { +@Slf4j +public class EV3Led extends EV3DevDevice implements LED, Closeable { /** * Directions of the LED. @@ -22,15 +26,23 @@ public enum Direction { RIGHT } - private static final Logger log = LoggerFactory.getLogger(EV3Led.class); + private static final Direction[] directionArray = {Direction.LEFT,Direction.RIGHT}; + /** + * @deprecated Use EV3LedDirection.LEFT instead. + */ + @Deprecated public static final int LEFT = 0; + /** + * @deprecated Use EV3Led.Direction.RIGHT instead. + */ + @Deprecated public static final int RIGHT = 1; private final Direction direction; - private final String LED_RED; - private final String LED_GREEN; + private final DataChannelRewriter redWriter; + private final DataChannelRewriter greenWriter; /** * Create an EV3LED object associated with the LED of the specified direction. @@ -43,18 +55,19 @@ public EV3Led(final Direction direction) { //TODO Refactor if (direction == null) { - log.error("You are not specifying any button."); + LOGGER.error("You are not specifying any button."); throw new IllegalArgumentException("You are not specifying any button."); } this.direction = direction; + String ledPath = EV3DevFileSystem.getRootPath(); if (direction == Direction.LEFT) { - LED_RED = ev3DevProperties.getProperty("ev3.led.left.red"); - LED_GREEN = ev3DevProperties.getProperty("ev3.led.left.green"); + redWriter = new DataChannelRewriter(ledPath + ev3DevProperties.getProperty("ev3.led.left.red")); + greenWriter = new DataChannelRewriter(ledPath + ev3DevProperties.getProperty("ev3.led.left.green")); } else { - LED_RED = ev3DevProperties.getProperty("ev3.led.right.red"); - LED_GREEN = ev3DevProperties.getProperty("ev3.led.right.green"); + redWriter = new DataChannelRewriter(ledPath + ev3DevProperties.getProperty("ev3.led.right.red")); + greenWriter = new DataChannelRewriter(ledPath + ev3DevProperties.getProperty("ev3.led.right.green")); } } @@ -65,22 +78,9 @@ public EV3Led(final Direction direction) { * @throws RuntimeException if LED feature is not supported on the current platform. * @deprecated Use {@link #EV3Led(Direction)} instead. */ + @Deprecated public EV3Led(final int button) { - checkPlatform(); - - if (button == LEFT) { - LED_RED = ev3DevProperties.getProperty("ev3.led.left.red"); - LED_GREEN = ev3DevProperties.getProperty("ev3.led.left.green"); - direction = Direction.LEFT; - } else if (button == RIGHT) { - LED_RED = ev3DevProperties.getProperty("ev3.led.right.red"); - LED_GREEN = ev3DevProperties.getProperty("ev3.led.right.green"); - direction = Direction.RIGHT; - } else { - log.error("You are not specifying any button."); - throw new IllegalArgumentException("You are not specifying any button."); - } - + this(directionArray[button]); } /** @@ -90,13 +90,12 @@ public EV3Led(final int button) { */ private void checkPlatform() { if (!CURRENT_PLATFORM.equals(EV3DevPlatform.EV3BRICK)) { - log.error("This actuator is specific of: {}", EV3DevPlatform.EV3BRICK); + LOGGER.error("This actuator is specific of: {}", EV3DevPlatform.EV3BRICK); throw new RuntimeException("This actuator is specific of: " + EV3DevPlatform.EV3BRICK); } } //TODO Add Enums for patterns - /** * Sets the pattern of light to be shown with this LED. * @@ -109,21 +108,24 @@ private void checkPlatform() { */ @Override public void setPattern(final int pattern) { - //Off + + final String off = Integer.toString(0); + final String on = Integer.toString(255); + if (pattern == 0) { - Sysfs.writeInteger(LED_RED, 0); - Sysfs.writeInteger(LED_GREEN, 0); + greenWriter.writeString(off); + redWriter.writeString(off); } else if (pattern == 1) { - Sysfs.writeInteger(LED_RED, 0); - Sysfs.writeInteger(LED_GREEN, 255); + greenWriter.writeString(on); + redWriter.writeString(off); } else if (pattern == 2) { - Sysfs.writeInteger(LED_RED, 255); - Sysfs.writeInteger(LED_GREEN, 0); + greenWriter.writeString(off); + redWriter.writeString(on); } else if (pattern == 3) { - Sysfs.writeInteger(LED_RED, 255); - Sysfs.writeInteger(LED_GREEN, 255); + greenWriter.writeString(on); + redWriter.writeString(on); } else if (pattern > 3) { - log.debug("This feature is not implemented"); + LOGGER.debug("This feature is not implemented"); } } @@ -135,4 +137,12 @@ public void setPattern(final int pattern) { public Direction getDirection() { return direction; } + + @Override + public void close() throws IOException { + greenWriter.close(); + redWriter.close(); + } + + } diff --git a/src/main/java/ev3dev/sensors/Battery.java b/src/main/java/ev3dev/sensors/Battery.java index 55c0c225..e55d5c7d 100644 --- a/src/main/java/ev3dev/sensors/Battery.java +++ b/src/main/java/ev3dev/sensors/Battery.java @@ -3,10 +3,12 @@ import ev3dev.hardware.EV3DevDevice; import ev3dev.hardware.EV3DevFileSystem; import ev3dev.hardware.EV3DevPlatform; -import ev3dev.utils.Sysfs; +import ev3dev.utils.DataChannelRereader; import lejos.hardware.Power; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import lombok.extern.slf4j.Slf4j; + +import java.io.Closeable; +import java.io.IOException; /** * The class Battery interacts with EV3Dev to get information about battery used. @@ -15,21 +17,11 @@ * @see https://www.kernel.org/doc/Documentation/power/power_supply_class.txt * @see https://github.com/ev3dev/ev3dev-lang/blob/develop/wrapper-specification.md#direct-attribute-mappings-5 */ -public class Battery extends EV3DevDevice implements Power { - - private static final Logger LOGGER = LoggerFactory.getLogger(Battery.class); - - private final String BATTERY; - private final String BATTERY_EV3; - private final String BATTERY_PISTORMS; - private final String BATTERY_BRICKPI; - private final String BATTERY_BRICKPI3; +@Slf4j +public class Battery extends EV3DevDevice implements Power, Closeable { - private String BATTERY_PATH; - private final String VOLTAGE = "voltage_now"; - private final String CURRENT = "current_now"; - - private String BATTERY_PATH_LOCAL = ""; + private final DataChannelRereader voltageRereader; + private final DataChannelRereader currentRereader; private static Battery instance; @@ -51,53 +43,61 @@ private Battery() { LOGGER.debug("Init sensor"); - BATTERY = ev3DevProperties.getProperty("battery"); - BATTERY_EV3 = ev3DevProperties.getProperty("ev3.battery"); - BATTERY_PISTORMS = ev3DevProperties.getProperty("pistorms.battery"); - BATTERY_BRICKPI = ev3DevProperties.getProperty("brickpi.battery"); - BATTERY_BRICKPI3 = ev3DevProperties.getProperty("brickpi3.battery"); + String battery = ev3DevProperties.getProperty("battery"); + String batteryEv3 = ev3DevProperties.getProperty("ev3.battery"); + String batteryPistorms = ev3DevProperties.getProperty("pistorms.battery"); + String batteryBrickpi = ev3DevProperties.getProperty("brickpi.battery"); + String batteryBrickpi3 = ev3DevProperties.getProperty("brickpi3.battery"); //TODO Create separator variable for the whole project - BATTERY_PATH = EV3DevFileSystem.getRootPath() + "/" + BATTERY; + String batteryPath = EV3DevFileSystem.getRootPath() + "/" + battery; + String batteryPathLocal = ""; if (CURRENT_PLATFORM.equals(EV3DevPlatform.EV3BRICK)) { - BATTERY_PATH_LOCAL += BATTERY_PATH + "/" + BATTERY_EV3; + batteryPathLocal += batteryPath + "/" + batteryEv3; } else if (CURRENT_PLATFORM.equals(EV3DevPlatform.PISTORMS)) { - BATTERY_PATH_LOCAL += BATTERY_PATH + "/" + BATTERY_PISTORMS; + batteryPathLocal += batteryPath + "/" + batteryPistorms; } else if (CURRENT_PLATFORM.equals(EV3DevPlatform.BRICKPI)) { - BATTERY_PATH_LOCAL += BATTERY_PATH + "/" + BATTERY_BRICKPI; + batteryPathLocal += batteryPath + "/" + batteryBrickpi; } else if (CURRENT_PLATFORM.equals(EV3DevPlatform.BRICKPI3)) { - BATTERY_PATH_LOCAL += BATTERY_PATH + "/" + BATTERY_BRICKPI3; + batteryPathLocal += batteryPath + "/" + batteryBrickpi3; } + String voltage = "voltage_now"; + voltageRereader = new DataChannelRereader(batteryPathLocal + "/" + voltage); + String current = "current_now"; + currentRereader = new DataChannelRereader(batteryPath + "/" + batteryEv3 + "/" + current); + } + + public int getVoltageMicroVolt() { + return Integer.parseInt(voltageRereader.readString()); } + /** + * @return voltage of the battery in millivolts. + */ @Override public int getVoltageMilliVolt() { - return (int) Sysfs.readFloat(BATTERY_PATH_LOCAL + "/" + VOLTAGE) / 1000; + return getVoltageMicroVolt() / 1000; } /** - * Returns voltage of the battery in microvolts. - * - * @return voltage + * @return voltage of the battery in microvolts. */ public float getVoltage() { - return Sysfs.readFloat(BATTERY_PATH_LOCAL + "/" + VOLTAGE) / 1000000; + return getVoltageMicroVolt() / 1000000f; } //TODO Review output //TODO Review units /** - * Returns the current of the battery in amps. - * - * @return current + * @return current from the battery in amps, or Float.NaN if run on something other than EV3BRICK */ public float getBatteryCurrent() { if (CURRENT_PLATFORM.equals(EV3DevPlatform.EV3BRICK)) { - return Sysfs.readFloat(BATTERY_PATH + "/" + BATTERY_EV3 + "/" + CURRENT); + return Float.parseFloat(currentRereader.readString()); } else { LOGGER.warn("This method is not available for {} & {}", EV3DevPlatform.PISTORMS, EV3DevPlatform.BRICKPI); - return -1f; + return Float.NaN; } } @@ -107,4 +107,9 @@ public float getMotorCurrent() { throw new UnsupportedOperationException("This feature is not implemented"); } + @Override + public void close() throws IOException { + voltageRereader.close(); + currentRereader.close(); + } } diff --git a/src/main/java/ev3dev/utils/DataChannelRereader.java b/src/main/java/ev3dev/utils/DataChannelRereader.java index 2ba369f6..36066091 100644 --- a/src/main/java/ev3dev/utils/DataChannelRereader.java +++ b/src/main/java/ev3dev/utils/DataChannelRereader.java @@ -25,38 +25,38 @@ public class DataChannelRereader implements Closeable { * * @param path path to the file to reread * @param bufferLength length of the buffer to hold the structure - * @throws IOException when things go wrong */ - public DataChannelRereader(Path path, int bufferLength) throws IOException { + public DataChannelRereader(Path path, int bufferLength) { this.path = path; this.byteBuffer = ByteBuffer.allocate(bufferLength); - this.channel = FileChannel.open(path); + try { + this.channel = FileChannel.open(path); + } catch (IOException e) { + throw new RuntimeException("Problem opening path: " + path, e); + } } /** * Create a DataChannelRereader for pathString with the default 32-byte buffer. * * @param pathString Path to the file to reread - * @throws IOException when things go wrong */ - public DataChannelRereader(String pathString) throws IOException { + public DataChannelRereader(String pathString) { this(Paths.get(pathString),32); } /** * @return a string made from the bytes in the file; */ - public String readString() { + public synchronized String readString() { try { - int n; - do { - byteBuffer.clear(); - channel.position(0); - n = channel.read(byteBuffer); - if (n == -1) { - throw new IOException("Premature end of file "); - } - } while (n <= 0); + byteBuffer.clear(); + int n = channel.read(byteBuffer,0); + if ((n == -1) || (n == 0)) { + return ""; + } else if (n < -1) { + throw new RuntimeException("Unexpected read byte count of " + n + " while reading " + path); + } byte[] bytes = byteBuffer.array(); if (bytes[n - 1] == '\n') { @@ -69,8 +69,12 @@ public String readString() { } } + public Path getPath() { + return path; + } + @Override - public void close() throws IOException { + public synchronized void close() throws IOException { channel.close(); } } diff --git a/src/main/java/ev3dev/utils/DataChannelRewriter.java b/src/main/java/ev3dev/utils/DataChannelRewriter.java new file mode 100644 index 00000000..3a5a1f09 --- /dev/null +++ b/src/main/java/ev3dev/utils/DataChannelRewriter.java @@ -0,0 +1,75 @@ +package ev3dev.utils; + +import java.io.Closeable; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; + +/** + * Writer of streams that can rewrite the same channel for structured data of + * known length. The focus of this class is on performance. + * + * @author David Walend + */ +public class DataChannelRewriter implements Closeable { + + private final Path path; + private final ByteBuffer byteBuffer; + private final FileChannel channel; + + /** + * Create a DataChannelRewriter for path with a bufferLength byte buffer + * + * @param path path to the file to reread + * @param bufferLength length of the buffer to hold the structure + */ + public DataChannelRewriter(Path path, int bufferLength) { + this.path = path; + this.byteBuffer = ByteBuffer.allocate(bufferLength); + try { + this.channel = FileChannel.open(path, StandardOpenOption.WRITE); + } catch (IOException e) { + throw new RuntimeException("While opening " + path,e); + } + } + + /** + * Create a DataChannelRewriter for pathString with the default 32-byte buffer. + * + * @param pathString Path to the file to reread + */ + public DataChannelRewriter(String pathString) { + this(Paths.get(pathString),32); + } + + /** + * @param string to write. A new line character + */ + public synchronized void writeString(String string) { + try { + byteBuffer.clear(); + byteBuffer.put(string.getBytes(StandardCharsets.UTF_8)); + byteBuffer.put(((byte)'\n')); + byteBuffer.flip(); + channel.truncate(0); + channel.write(byteBuffer,0); + channel.force(false); + } catch (IOException e) { + throw new RuntimeException("Problem writing path: " + path, e); + } + } + + public Path getPath() { + return path; + } + + @Override + public synchronized void close() throws IOException { + channel.close(); + } +} + diff --git a/src/main/resources/jessie.properties b/src/main/resources/jessie.properties index f6044b38..4024e53c 100644 --- a/src/main/resources/jessie.properties +++ b/src/main/resources/jessie.properties @@ -43,10 +43,10 @@ pistorms.sensor.port.3=pistorms:BAS1 pistorms.sensor.port.4=pistorms:BAS2 #LED -ev3.led.left.red=/sys/class/leds/ev3:left:red:ev3dev/brightness -ev3.led.left.green=/sys/class/leds/ev3:left:green:ev3dev/brightness -ev3.led.right.red=/sys/class/leds/ev3:right:red:ev3dev/brightness -ev3.led.right.green=/sys/class/leds/ev3:right:green:ev3dev/brightness +ev3.led.left.red=/leds/ev3:left:red:ev3dev/brightness +ev3.led.left.green=/leds/ev3:left:green:ev3dev/brightness +ev3.led.right.red=/leds/ev3:right:red:ev3dev/brightness +ev3.led.right.green=/leds/ev3:right:green:ev3dev/brightness #KEY ev3.key=/dev/input/by-path/platform-gpio-keys.0-event diff --git a/src/main/resources/stretch.properties b/src/main/resources/stretch.properties index fc54fe91..801dd7e6 100644 --- a/src/main/resources/stretch.properties +++ b/src/main/resources/stretch.properties @@ -43,10 +43,10 @@ pistorms.sensor.port.3=pistorms:BAS1 pistorms.sensor.port.4=pistorms:BAS2 #LED -ev3.led.left.red=/sys/class/leds/led0:red:brick-status/brightness -ev3.led.left.green=/sys/class/leds/led0:green:brick-status/brightness -ev3.led.right.red=/sys/class/leds/led1:red:brick-status/brightness -ev3.led.right.green=/sys/class/leds/led1:green:brick-status/brightness +ev3.led.left.red=/leds/led0:red:brick-status/brightness +ev3.led.left.green=/leds/led0:green:brick-status/brightness +ev3.led.right.red=/leds/led1:red:brick-status/brightness +ev3.led.right.green=/leds/led1:green:brick-status/brightness #KEY ev3.key=/dev/input/by-path/platform-gpio_keys-event diff --git a/src/test/java/ev3dev/actuators/ev3/EV3LedTest.java b/src/test/java/ev3dev/actuators/ev3/EV3LedTest.java index b83351f2..0e8358cb 100644 --- a/src/test/java/ev3dev/actuators/ev3/EV3LedTest.java +++ b/src/test/java/ev3dev/actuators/ev3/EV3LedTest.java @@ -2,90 +2,95 @@ import ev3dev.hardware.EV3DevFileSystem; import ev3dev.hardware.EV3DevPlatform; -import fake_ev3dev.ev3dev.actuators.FakeLed; +import fake_ev3dev.ev3dev.actuators.ev3.FakeLed; import fake_ev3dev.ev3dev.sensors.FakeBattery; import lejos.hardware.LED; -import org.junit.*; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; import org.junit.rules.ExpectedException; import java.io.IOException; +import static org.assertj.core.api.BDDAssertions.then; + public class EV3LedTest { + //TODO Refactor exception cases with JUnit 5 @Rule public ExpectedException thrown = ExpectedException.none(); @Before - public void resetTest() throws IOException, NoSuchFieldException, IllegalAccessException { - - //Reset the singleton - //https://stackoverflow.com/questions/8256989/singleton-and-unit-testing - //Field instance = Sound.class.getDeclaredField("instance"); - //instance.setAccessible(true); - //instance.set(null, null); - - FakeBattery.resetEV3DevInfrastructure(); + public void resetTest() throws IOException { System.setProperty(EV3DevFileSystem.EV3DEV_TESTING_KEY, FakeBattery.EV3DEV_FAKE_SYSTEM_PATH); + FakeBattery.resetEV3DevInfrastructure(); + new FakeBattery(EV3DevPlatform.EV3BRICK); } @Test - public void constructorLeftTest() throws Exception { - - final FakeBattery fakeBattery = new FakeBattery(EV3DevPlatform.EV3BRICK); - - LED led = new EV3Led(EV3Led.LEFT); - led = new EV3Led(EV3Led.Direction.LEFT); - } + public void given_actuator_when_useConstructorLeft_then_Ok() throws Exception { - @Test - public void constructorRightTest() throws Exception { + //Given + FakeLed fakeLed = new FakeLed(EV3DevPlatform.EV3BRICK); - final FakeBattery fakeBattery = new FakeBattery(EV3DevPlatform.EV3BRICK); + //When + @SuppressWarnings("deprecation") LED led = new EV3Led(EV3Led.LEFT); + LED led2 = new EV3Led(EV3Led.Direction.LEFT); - LED led = new EV3Led(EV3Led.RIGHT); - led = new EV3Led(EV3Led.Direction.RIGHT); + //Then + then(led).isNotNull(); + then(led2).isNotNull(); } - @Ignore("Review how to reset a Static classic in JUnit") @Test - public void usingLedOnEV3BrickPlatformTest() throws Exception { + public void given_actuator_when_useConstructorRight_then_Ok() throws Exception { - thrown.expect(RuntimeException.class); + //Given + FakeLed fakeLed = new FakeLed(EV3DevPlatform.EV3BRICK); - final FakeBattery fakeBattery = new FakeBattery(EV3DevPlatform.BRICKPI); + //When + @SuppressWarnings("deprecation") LED led = new EV3Led(EV3Led.RIGHT); + LED led2 = new EV3Led(EV3Led.Direction.RIGHT); - LED led = new EV3Led(EV3Led.LEFT); + //Then + then(led).isNotNull(); + then(led2).isNotNull(); } @Test - public void badButtonTest() throws Exception { - - thrown.expect(RuntimeException.class); + public void given_actuator_when_useConstructorWithBadParameter_then_Ko() throws Exception { - final FakeBattery fakeBattery = new FakeBattery(EV3DevPlatform.EV3BRICK); + //Given + FakeLed fakeLed = new FakeLed(EV3DevPlatform.EV3BRICK); - LED led = new EV3Led(2); + //When + //Then + thrown.expect(ArrayIndexOutOfBoundsException.class); + @SuppressWarnings("deprecation") LED led = new EV3Led(4); } @Test - public void badDirectionTest() throws Exception { - - thrown.expect(IllegalArgumentException.class); + public void given_actuator_when_useConstructorWithNull_then_Ko() throws Exception { - final FakeBattery fakeBattery = new FakeBattery(EV3DevPlatform.EV3BRICK); + //Given + FakeLed fakeLed = new FakeLed(EV3DevPlatform.EV3BRICK); + //When + //Then + thrown.expect(IllegalArgumentException.class); LED led = new EV3Led(null); } @Test - public void leftLedPatternsTest() throws Exception { + public void given_actuator_when_leftPatterns_then_Ok() throws Exception { - final FakeBattery fakeBattery = new FakeBattery(EV3DevPlatform.EV3BRICK); - final FakeLed fakeLed = new FakeLed(EV3DevPlatform.EV3BRICK); + //Given + FakeLed fakeLed = new FakeLed(EV3DevPlatform.EV3BRICK); - LED led = new EV3Led(EV3Led.LEFT); + //When + LED led = new EV3Led(EV3Led.Direction.LEFT); led.setPattern(1); led.setPattern(2); led.setPattern(3); @@ -96,15 +101,19 @@ public void leftLedPatternsTest() throws Exception { led.setPattern(2); led.setPattern(3); led.setPattern(4); + + //Then + //TODO Currently, it is not possible to execute a verify or something similar } @Test - public void rightLedPatternsTest() throws Exception { + public void given_actuator_when_rightPatterns_then_Ok() throws Exception { - final FakeBattery fakeBattery = new FakeBattery(EV3DevPlatform.EV3BRICK); - final FakeLed fakeLed = new FakeLed(EV3DevPlatform.EV3BRICK); + //Given + FakeLed fakeLed = new FakeLed(EV3DevPlatform.EV3BRICK); - LED led = new EV3Led(EV3Led.RIGHT); + //When + @SuppressWarnings("deprecation") LED led = new EV3Led(EV3Led.RIGHT); led.setPattern(1); led.setPattern(2); led.setPattern(3); @@ -115,19 +124,25 @@ public void rightLedPatternsTest() throws Exception { led.setPattern(2); led.setPattern(3); led.setPattern(4); + + //Then + //TODO Currently, it is not possible to execute a verify or something similar } @Test - public void getDirectionTest() throws Exception { + public void given_actuator_when_getDirection_then_Ok() throws Exception { - final FakeBattery fakeBattery = new FakeBattery(EV3DevPlatform.EV3BRICK); - final FakeLed fakeLed = new FakeLed(EV3DevPlatform.EV3BRICK); + //Given + FakeLed fakeLed = new FakeLed(EV3DevPlatform.EV3BRICK); - EV3Led led = new EV3Led(EV3Led.RIGHT); - Assert.assertEquals(EV3Led.Direction.RIGHT, led.getDirection()); + //When + @SuppressWarnings("deprecation") EV3Led led = new EV3Led(EV3Led.RIGHT); + EV3Led led2 = new EV3Led(EV3Led.Direction.RIGHT); - led = new EV3Led(EV3Led.Direction.RIGHT); - Assert.assertEquals(EV3Led.Direction.RIGHT, led.getDirection()); + //Then + EV3Led.Direction expectdedDirection = EV3Led.Direction.RIGHT; + then(led.getDirection()).isEqualTo(expectdedDirection); + then(led2.getDirection()).isEqualTo(expectdedDirection); } } diff --git a/src/test/java/ev3dev/utils/DataChannelRereaderConcurrencyTest.java b/src/test/java/ev3dev/utils/DataChannelRereaderConcurrencyTest.java new file mode 100644 index 00000000..34106caa --- /dev/null +++ b/src/test/java/ev3dev/utils/DataChannelRereaderConcurrencyTest.java @@ -0,0 +1,279 @@ +package ev3dev.utils; + +import java.io.File; +import java.util.concurrent.CompletableFuture; +import java.util.stream.IntStream; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; + +import static org.assertj.core.api.BDDAssertions.then; + +@Slf4j +public class DataChannelRereaderConcurrencyTest { + + final String fileName = "./pairs.txt"; + final String fileName2 = "./odds.txt"; + final Integer limit = 1000; + + @Before + @SneakyThrows + public void createFiles() { + new File(fileName).createNewFile(); + new File(fileName2).createNewFile(); + } + + /** + * Writer1 -> pairs.txt + * Reader1 <- pairs.txt + * Reader2 <- pairs.txt + * + * Writer1 -> odds.txt + * Reader1 <- odds.txt + * Reader2 <- odds.txt + */ + @Ignore + @Test + public void given_multiple_DataChannelRereader_when_execute_concurrently_then_Ok() { + + CompletableFuture request1 = asyncWriteFile(true); + CompletableFuture request2 = asyncWriteFile(false); + CompletableFuture request3 = asyncReadFile(true); + CompletableFuture request4 = asyncReadFile(false); + CompletableFuture request5 = asyncReadFile2(true); + CompletableFuture request6 = asyncReadFile2(false); + + CompletableFuture combinedFuture = CompletableFuture.allOf( + request1, + request2, + request3, + request4, + request5, + request6); + + combinedFuture.join(); + + then(request1.isDone()).isTrue(); + then(request2.isDone()).isTrue(); + then(request3.isDone()).isTrue(); + then(request4.isDone()).isTrue(); + then(request5.isDone()).isTrue(); + then(request6.isDone()).isTrue(); + + then(request1.join()).isEqualTo("Ok asyncWriteFile"); + then(request2.join()).isEqualTo("Ok asyncWriteFile"); + then(request3.join()).isEqualTo("Ok asyncReadFile"); + then(request4.join()).isEqualTo("Ok asyncReadFile"); + then(request5.join()).isEqualTo("Ok asyncReadFile2"); + then(request6.join()).isEqualTo("Ok asyncReadFile2"); + + System.out.println("End"); + } + + private void readFile(String file, Boolean flag) { + Integer value = Sysfs.readInteger(file); + if (flag) { + then(value % 2 == 0).isTrue(); + } else { + then(value % 2 != 0).isTrue(); + } + } + + private void readFile2(String file, Boolean flag) { + DataChannelRereader dataChannelRereader = new DataChannelRereader(file); + Integer value = Integer.parseInt(dataChannelRereader.readString()); + if (flag) { + then(value % 2 == 0).isTrue(); + } else { + then(value % 2 != 0).isTrue(); + } + } + + private CompletableFuture asyncReadFile(boolean flag) { + CompletableFuture cf1 = CompletableFuture.supplyAsync(() -> { + IntStream + .rangeClosed(1, limit) + .forEach(i -> { + if (flag) { + readFile(fileName, flag); + } else { + readFile(fileName2, flag); + } + }); + return "Ok asyncReadFile"; + }) + .handle((input, exception) -> { + if (exception != null) { + LOGGER.warn(exception.getLocalizedMessage(), exception); + return "Ko asyncReadFile"; + } + return input; + }); + return cf1; + } + + private CompletableFuture asyncReadFile2(boolean flag) { + CompletableFuture cf = CompletableFuture.supplyAsync(() -> { + IntStream + .rangeClosed(1, limit) + .forEach(i -> { + if (flag) { + readFile2(fileName, flag); + } else { + readFile2(fileName2, flag); + } + }); + return "Ok asyncReadFile2"; + }) + .handle((input, exception) -> { + if (exception != null) { + LOGGER.warn(exception.getLocalizedMessage(), exception); + return "Ko asyncReadFile2"; + } + return input; + }); + return cf; + } + + private void writeFile(String file, String value) { + Sysfs.writeString(file, value); + } + + private CompletableFuture asyncWriteFile(boolean flag) { + CompletableFuture cf = CompletableFuture.supplyAsync(() -> { + IntStream + .rangeClosed(1, limit) + .filter(i -> { + if (flag) { + return i % 2 == 0; + } else { + return i % 2 != 0; + } + }) + .forEach(i -> { + if (flag) { + writeFile(fileName, String.valueOf(i)); + } else { + writeFile(fileName2, String.valueOf(i)); + } + }); + return "Ok asyncWriteFile"; + }) + .handle((input, exception) -> { + if (exception != null) { + LOGGER.warn(exception.getLocalizedMessage(), exception); + return "Ko asyncWriteFile"; + } + return input; + }); + + return cf; + } + + /** + * Writer1 -> pairs.txt + * Reader1 <- pairs.txt + * + * Writer1 -> odds.txt + * Reader1 <- odds.txt + */ + @Ignore + @Test + public void given_multiple_DataChannelRereader_when_execute_concurrently_then_Ok2() { + + CompletableFuture request1 = asyncWriteFile(true); + CompletableFuture request2 = asyncWriteFile(false); + CompletableFuture request3 = asyncReadFile(true); + CompletableFuture request4 = asyncReadFile(false); + + CompletableFuture combinedFuture = CompletableFuture.allOf( + request1, + request2, + request3, + request4); + + combinedFuture.join(); + + then(request1.isDone()).isTrue(); + then(request2.isDone()).isTrue(); + then(request3.isDone()).isTrue(); + then(request4.isDone()).isTrue(); + + then(request1.join()).isEqualTo("Ok asyncWriteFile"); + then(request2.join()).isEqualTo("Ok asyncWriteFile"); + then(request3.join()).isEqualTo("Ok asyncReadFile"); + then(request4.join()).isEqualTo("Ok asyncReadFile"); + + System.out.println("End"); + } + + /** + * Reader1 <- pairs.txt + * Reader1 <- odds.txt + */ + @Test + public void given_multiple_DataChannelRereader_when_execute_concurrently_then_Ok3() { + + writeFile(fileName, "2"); + writeFile(fileName2, "1"); + + CompletableFuture request1 = asyncReadFile(true); + CompletableFuture request2 = asyncReadFile(false); + + CompletableFuture combinedFuture = CompletableFuture.allOf( + request1, + request2); + + combinedFuture.join(); + + then(request1.isDone()).isTrue(); + then(request2.isDone()).isTrue(); + + then(request1.join()).isEqualTo("Ok asyncReadFile"); + then(request2.join()).isEqualTo("Ok asyncReadFile"); + + System.out.println("End"); + } + + /** + * Writer1 -> pairs.txt Once + * Writer1 -> odds.txt Once + * Reader1 <- pairs.txt + * Reader1 <- odds.txt + * Reader2 <- pairs.txt + * Reader2 <- odds.txt + */ + @Test + public void given_multiple_DataChannelRereader_when_execute_concurrently_then_Ok4() { + + writeFile(fileName, "2"); + writeFile(fileName2, "1"); + + CompletableFuture request1 = asyncReadFile(true); + CompletableFuture request2 = asyncReadFile(false); + CompletableFuture request3 = asyncReadFile2(true); + CompletableFuture request4 = asyncReadFile2(false); + + CompletableFuture combinedFuture = CompletableFuture.allOf( + request1, + request2, + request3, + request4); + + combinedFuture.join(); + + then(request1.isDone()).isTrue(); + then(request2.isDone()).isTrue(); + then(request3.isDone()).isTrue(); + then(request4.isDone()).isTrue(); + + then(request1.join()).isEqualTo("Ok asyncReadFile"); + then(request2.join()).isEqualTo("Ok asyncReadFile"); + then(request3.join()).isEqualTo("Ok asyncReadFile2"); + then(request4.join()).isEqualTo("Ok asyncReadFile2"); + + System.out.println("End"); + } +} diff --git a/src/test/java/ev3dev/utils/DataChannelRereaderRaceTest.java b/src/test/java/ev3dev/utils/DataChannelRereaderRaceTest.java new file mode 100644 index 00000000..1df8adcd --- /dev/null +++ b/src/test/java/ev3dev/utils/DataChannelRereaderRaceTest.java @@ -0,0 +1,78 @@ +package ev3dev.utils; + +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.concurrent.CompletableFuture; +import java.util.stream.IntStream; + +import static org.assertj.core.api.BDDAssertions.then; + +@Slf4j +public class DataChannelRereaderRaceTest { + + final String fileName = "./race.txt"; + final Integer limit = 10000; + + @Before + @SneakyThrows + public void createFiles() throws IOException { + Files.writeString(Path.of(fileName), "test1234"); + } + + /** + * Reader1 <- race.txt + * Reader2 <- race.txt + * Reader3 <- race.txt + * Reader4 <- race.txt + */ + @Test + public void given_multiple_DataChannelRereader_when_execute_concurrently_then_Ok() { + DataChannelRereader reader = new DataChannelRereader(fileName); + + CompletableFuture rq1 = asyncReadFile(reader); + CompletableFuture rq2 = asyncReadFile(reader); + CompletableFuture rq3 = asyncReadFile(reader); + CompletableFuture rq4 = asyncReadFile(reader); + + CompletableFuture combinedFuture = CompletableFuture.allOf(rq1, rq2, rq3, rq4); + combinedFuture.join(); + + then(rq1.isDone()).isTrue(); + then(rq2.isDone()).isTrue(); + then(rq3.isDone()).isTrue(); + then(rq4.isDone()).isTrue(); + + then(rq1.join()).isEqualTo("Ok asyncReadFile"); + then(rq2.join()).isEqualTo("Ok asyncReadFile"); + then(rq3.join()).isEqualTo("Ok asyncReadFile"); + then(rq4.join()).isEqualTo("Ok asyncReadFile"); + System.out.println("End"); + } + + private void readFile(DataChannelRereader reader) { + then(reader.readString()).isEqualTo("test1234"); + } + + private CompletableFuture asyncReadFile(DataChannelRereader reader) { + CompletableFuture cf1 = CompletableFuture.supplyAsync(() -> { + IntStream + .rangeClosed(1, limit) + .forEach(i -> readFile(reader)); + return "Ok asyncReadFile"; + }) + .handle((input, exception) -> { + if (exception != null) { + LOGGER.warn(exception.getLocalizedMessage(), exception); + return "Ko asyncReadFile"; + } + return input; + }); + return cf1; + } +} diff --git a/src/test/java/ev3dev/utils/DataChannelRereaderTest.java b/src/test/java/ev3dev/utils/DataChannelRereaderTest.java new file mode 100644 index 00000000..86a8352b --- /dev/null +++ b/src/test/java/ev3dev/utils/DataChannelRereaderTest.java @@ -0,0 +1,89 @@ +package ev3dev.utils; + +import java.io.IOException; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.Assert.assertEquals; + +/** + * Some tests of DataChannelRereader. + * + * @author David Walend + */ +public class DataChannelRereaderTest { + + static Path tempDirectory; + static Path testPath; + static final String testString = "Written String"; + static final String differentTestString = "Different String"; + + @BeforeClass + public static void createFiles() throws IOException { + tempDirectory = Files.createTempDirectory("DataChannelRereaderTest"); + testPath = Files.createFile(Path.of(tempDirectory.toString(),"testFile")); + Files.write(testPath, testString.getBytes()); + } + + @Before + public void writeFile() throws IOException { + Files.write(testPath, testString.getBytes()); + } + + @AfterClass + public static void cleanupFiles() throws IOException { + Files.delete(testPath); + Files.delete(tempDirectory); + } + + + @Test + public void testOpenClose() throws IOException { + DataChannelRereader rereader = new DataChannelRereader(testPath,32); + rereader.close(); + } + + @Test + public void testOpenReadClose() throws IOException { + DataChannelRereader rereader = new DataChannelRereader(testPath,32); + String readString = rereader.readString(); + rereader.close(); + + assertEquals(testString,readString); + } + + @Test + public void testClosable() throws IOException { + try(DataChannelRereader rereader = new DataChannelRereader(testPath,32)){ + String readString = rereader.readString(); + assertEquals(testString,readString); + } + } + + @Test + public void testOpenReadTwoThingsClose() throws IOException { + DataChannelRereader rereader = new DataChannelRereader(testPath,32); + String readString = rereader.readString(); + assertEquals(testString,readString); + + Files.write(testPath, differentTestString.getBytes()); + + String readString2 = rereader.readString(); + assertEquals(differentTestString,readString2); + + rereader.close(); + } + + @Test(expected = RuntimeException.class) + public void testOpenNonexistentFile() throws IOException { + Path badPath = Path.of("/does/not/exist"); + + DataChannelRereader rereader = new DataChannelRereader(badPath,32); + rereader.close(); + } +} diff --git a/src/test/java/ev3dev/utils/DataChannelRewriterTest.java b/src/test/java/ev3dev/utils/DataChannelRewriterTest.java new file mode 100644 index 00000000..dcc534b1 --- /dev/null +++ b/src/test/java/ev3dev/utils/DataChannelRewriterTest.java @@ -0,0 +1,92 @@ +package ev3dev.utils; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.Assert.assertEquals; + +/** + * Some tests of DataChannelRewriter. + * + * @author David Walend + */ +public class DataChannelRewriterTest { + + static Path tempDirectory; + static Path testPath; + static final String startString = "Original String"; + static final String differentString = "Written String"; + + @BeforeClass + public static void createFiles() throws IOException { + tempDirectory = Files.createTempDirectory("DataChannelRewriterTest"); + testPath = Files.createFile(Path.of(tempDirectory.toString(),"testFile")); + Files.write(testPath, startString.getBytes()); + } + + @Before + public void writeFile() throws IOException { + Files.write(testPath, startString.getBytes()); + } + + @AfterClass + public static void cleanupFiles() throws IOException { + Files.delete(testPath); + Files.delete(tempDirectory); + } + + + @Test + public void testOpenClose() throws IOException { + DataChannelRewriter rewriter = new DataChannelRewriter(testPath,32); + rewriter.close(); + + assertEquals(startString,Files.readString(testPath)); + } + + @Test + public void testOpenReadClose() throws IOException { + DataChannelRewriter rewriter = new DataChannelRewriter(testPath,32); + rewriter.writeString(differentString); + rewriter.close(); + + assertEquals(differentString+"\n",Files.readString(testPath)); + } + + @Test + public void testClosable() throws IOException { + try(DataChannelRewriter rewriter = new DataChannelRewriter(testPath,32)){ + rewriter.writeString(differentString); + } + assertEquals(differentString+"\n",Files.readString(testPath)); + } + + @Test + public void testOpenWriteTwoThingsClose() throws IOException { + DataChannelRewriter rewriter = new DataChannelRewriter(testPath,32); + rewriter.writeString(differentString); + assertEquals(differentString+"\n",Files.readString(testPath)); + + String anotherString = "Another String"; + rewriter.writeString(anotherString); + + assertEquals(anotherString+"\n",Files.readString(testPath)); + + rewriter.close(); + } + + @Test(expected = RuntimeException.class) + public void testOpenNonexistentFile() throws IOException { + Path badPath = Path.of("/does/not/exist"); + + DataChannelRewriter rewriter = new DataChannelRewriter(badPath,32); + rewriter.writeString(differentString); + rewriter.close(); + } +} diff --git a/src/test/java/fake_ev3dev/ev3dev/actuators/FakeLed.java b/src/test/java/fake_ev3dev/ev3dev/actuators/ev3/FakeLed.java similarity index 82% rename from src/test/java/fake_ev3dev/ev3dev/actuators/FakeLed.java rename to src/test/java/fake_ev3dev/ev3dev/actuators/ev3/FakeLed.java index 23063021..c2478571 100644 --- a/src/test/java/fake_ev3dev/ev3dev/actuators/FakeLed.java +++ b/src/test/java/fake_ev3dev/ev3dev/actuators/ev3/FakeLed.java @@ -1,18 +1,21 @@ -package fake_ev3dev.ev3dev.actuators; +package fake_ev3dev.ev3dev.actuators.ev3; import ev3dev.hardware.EV3DevPlatform; +import ev3dev.utils.Shell; import fake_ev3dev.BaseElement; import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import lombok.extern.slf4j.Slf4j; +@Slf4j public class FakeLed extends BaseElement{ - public static final String LEFT_LED = "leds/ev3:left"; - public static final String RIGHT_LED = "leds/ev3:right"; - public static final String RED_LED = ":red:ev3dev"; - public static final String GREEN_LED = ":green:ev3dev"; + public static final String LEFT_LED = "leds/led0"; + public static final String RIGHT_LED = "leds/led1"; + public static final String RED_LED = ":red:brick-status"; + public static final String GREEN_LED = ":green:brick-status"; public static final String BRIGHTNESS = "brightness"; public FakeLed(final EV3DevPlatform ev3DevPlatform) throws IOException { @@ -71,6 +74,8 @@ public FakeLed(final EV3DevPlatform ev3DevPlatform) throws IOException { BRIGHTNESS); createFile(ledLeftRedBrightness); + var result = Shell.execute("tree " + EV3DEV_FAKE_SYSTEM_PATH); + LOGGER.info(result); } } diff --git a/src/test/resources/simplelogger.properties b/src/test/resources/simplelogger.properties new file mode 100644 index 00000000..a1143482 --- /dev/null +++ b/src/test/resources/simplelogger.properties @@ -0,0 +1,18 @@ +# SLF4J's SimpleLogger configuration file +# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. + +org.slf4j.simpleLogger.defaultLogLevel=info + +#org.slf4j.simpleLogger.log.ev3dev.hardware=trace +#org.slf4j.simpleLogger.log.ev3dev.utils=trace + + + +org.slf4j.simpleLogger.showDateTime=true +org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss +org.slf4j.simpleLogger.showThreadName=true +org.slf4j.simpleLogger.showLogName=true +org.slf4j.simpleLogger.showShortLogName=false + +org.slf4j.simpleLogger.logFile=System.err +#org.slf4j.simpleLogger.logFile=/home/robot/java/programs/logs.log