diff --git a/.github/workflows/pr-tests.yml b/.github/workflows/pr-tests.yml index 8c507688..d9a5bdfc 100644 --- a/.github/workflows/pr-tests.yml +++ b/.github/workflows/pr-tests.yml @@ -36,13 +36,99 @@ jobs: - name: Copy test files run: | - echo "Copying test data" - mkdir -p src/test/resources + echo "Copying hidden tests" + mkdir -p src/test/java src/test/resources + cp -R hidden-tests/src/test/java/. src/test/java/ cp -R hidden-tests/src/test/resources/. src/test/resources/ echo "" echo "Скопированы файлы:" - find src/test/resources -type f | sort | while read f; do echo " $f"; done + find src/test/java src/test/resources -type f | sort | while read f; do echo " $f"; done rm -rf hidden-tests - + + - name: Select test pattern from commit message + id: select_tests + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + if (!pr) { + core.setOutput('pattern', ''); + return; + } + const repo = { owner: context.repo.owner, repo: context.repo.repo }; + const { data: commit } = await github.rest.repos.getCommit({ + ...repo, + ref: pr.head.sha + }); + const msg = (commit.commit.message || '').toLowerCase(); + + const patterns = []; + const add = (p) => { if (!patterns.includes(p)) patterns.push(p); }; + + if (msg.startsWith('atm:')) add('**/atm/*Test'); + if (msg.startsWith('html:')) add('**/html/*Test'); + if (msg.startsWith('randomset:')) add('**/randomSet/*Test'); + if (msg.startsWith('cube:')) add('**/CubeSimpleTest'); + + core.setOutput('pattern', patterns.length ? patterns.join(',') : ''); + + - name: Run honors no-collections test + id: honors_test + continue-on-error: true + if: steps.select_tests.outputs.pattern == '**/randomSet/*Test' + run: mvn -B -Dtest=hse.java.lectures.lecture3.practice.randomSet.RandomSetBytecodeTest test + + - name: Comment on PR for honors solution + if: steps.honors_test.outcome == 'success' + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + if (!pr) { + core.info('No PR context, skipping comment.'); + return; + } + const body = [ + 'Поздравляем! 🎉', + '', + 'Вы решили сложную версию: решение прошло проверку «без коллекций».' + ].join('\n'); + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + body + }); + - name: Run tests - run: mvn -B test + run: | + if [ -n "${{ steps.select_tests.outputs.pattern }}" ]; then + echo "Running selected tests: ${{ steps.select_tests.outputs.pattern }}" + mvn -B -Dtest='${{ steps.select_tests.outputs.pattern }},!**/RandomSetBytecodeTest' test + else + echo "No task prefix found. Failing." + exit 1 + fi + + - name: Comment on PR when no task prefix + if: steps.select_tests.outputs.pattern == '' + uses: actions/github-script@v7 + with: + script: | + const pr = context.payload.pull_request; + if (!pr) { + core.info('No PR context, skipping comment.'); + return; + } + const body = [ + 'Тесты не запускались.', + '', + 'Укажите задачу в начале сообщения коммита:', + 'atm: ..., html: ..., randomset: ...' + ].join('\n'); + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: pr.number, + body + }); diff --git a/README-commit-format.md b/README-commit-format.md new file mode 100644 index 00000000..24c170a4 --- /dev/null +++ b/README-commit-format.md @@ -0,0 +1,20 @@ +# Формат коммитов для запуска тестов + +## Зачем это нужно +CI запускает тесты выборочно. Чтобы он знал, какие тесты запускать, сообщение коммита должно начинаться с названия задачи. + +## Формат +Используйте префикс в начале сообщения коммита: + +- `atm: ...` +- `html: ...` +- `randomset: ...` + +Тег задачи указывается в описании каждой задачи + +Пример: +``` +randomset: implement getRandom and contains +``` + +Если префикс не указан, CI **не запускает тесты** и оставляет комментарий в PR. diff --git a/commander/commander.iml b/commander/commander.iml deleted file mode 100644 index 68a9707e..00000000 --- a/commander/commander.iml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/pom.xml b/pom.xml index ade43fe2..648f8788 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,13 @@ ${junit.version} test + + + org.ow2.asm + asm + 9.8 + test + com.fasterxml.jackson.core @@ -65,4 +72,4 @@ - \ No newline at end of file + diff --git a/src/main/java/hse/java/lectures/lecture3/examples/Box.java b/src/main/java/hse/java/lectures/lecture3/examples/Box.java new file mode 100644 index 00000000..6c3ccf57 --- /dev/null +++ b/src/main/java/hse/java/lectures/lecture3/examples/Box.java @@ -0,0 +1,14 @@ +package hse.java.lectures.lecture3.examples; + +public class Box { + + private T item; + + public void put(T item) { + this.item = item; + } + + public T get() { + return item; + } +} diff --git a/src/main/java/hse/java/lectures/lecture3/examples/Generics.java b/src/main/java/hse/java/lectures/lecture3/examples/Generics.java new file mode 100644 index 00000000..4fe8a270 --- /dev/null +++ b/src/main/java/hse/java/lectures/lecture3/examples/Generics.java @@ -0,0 +1,43 @@ +package hse.java.lectures.lecture3.examples; + +import java.util.ArrayList; +import java.util.List; + +public class Generics { + + + + public T getType(T type) { + System.out.println(type.getClass()); + return type; + } + + public static void write(List list, T value) { + list.add(value); + } + + public static > T get(T value) { + return value; + } + + public static void testGenerics() { + // get(1.).compareTo() + write(List.of(1), 1); + write(List.of(), 1.2); + List ints = new ArrayList<>(); + ints.add(1); + ints.add(2); + // PECS + List nums = ints; + List si = ints; + si.add(1); + si.get(1); + } + + public static void main(String[] args) { + List il = new ArrayList<>(); + write(il, 5); + System.out.println(il); + } + +} diff --git a/src/main/java/hse/java/lectures/lecture3/examples/Main.java b/src/main/java/hse/java/lectures/lecture3/examples/Main.java new file mode 100644 index 00000000..116ff543 --- /dev/null +++ b/src/main/java/hse/java/lectures/lecture3/examples/Main.java @@ -0,0 +1,19 @@ +package hse.java.lectures.lecture3.examples; + +import java.util.ArrayList; +import java.util.concurrent.Callable; + +public class Main { + public static void main(String[] args) { + Box box = new Box<>(); + Box dBox = new Box<>(); + Callable callable; + + int x = new Integer(3); + Integer y = 5; + + Box nBox = box; + new Methods().get(new ArrayList()).add(1); + + } +} diff --git a/src/main/java/hse/java/lectures/lecture3/examples/Methods.java b/src/main/java/hse/java/lectures/lecture3/examples/Methods.java new file mode 100644 index 00000000..cac365d7 --- /dev/null +++ b/src/main/java/hse/java/lectures/lecture3/examples/Methods.java @@ -0,0 +1,10 @@ +package hse.java.lectures.lecture3.examples; + +public class Methods { + + + public T get(T type) { + return type; + } + +} diff --git a/src/main/java/hse/java/lectures/lecture3/examples/TryCatchThrows.java b/src/main/java/hse/java/lectures/lecture3/examples/TryCatchThrows.java new file mode 100644 index 00000000..0d972ac3 --- /dev/null +++ b/src/main/java/hse/java/lectures/lecture3/examples/TryCatchThrows.java @@ -0,0 +1,79 @@ +package hse.java.lectures.lecture3.examples; + +import java.io.*; + +public class TryCatchThrows { + + public void testRuntime() throws RuntimeException { + throw new RuntimeException("Test runtime"); + } + + public void testException() throws Exception { + throw new Exception("test exception"); + } + + public void testError() throws Error { + throw new Error("test error"); + } + + public void testTryRuntime() { + try { + testRuntime(); + } catch (RuntimeException e) { + System.out.println("Catch " + e.getMessage()); + } finally { + System.out.println("finally"); + } + } + + public void testTryException() { + try { + testException(); + } catch (Exception e) { + System.out.println("Catch " + e.getMessage()); + } finally { + System.out.println("finally"); + } + } + + public void testTryError() { + // testError(); + try { + testError(); + } catch (Error e) { + System.out.println("Catch " + e.getMessage()); + } finally { + System.out.println("finally"); + } + } + + public static void foo() { + throw new RuntimeException("My runtime"); + } + + public static void bar() throws Exception { + throw new Exception("My exception"); + } + + public static void main(String[] args) { + // foo(); +// try { +// bar(); +// } catch (Exception e) { +// System.err.println("Hello exception"); +// } finally { +// +// } + +// try (BufferedReader br = new BufferedReader(new FileReader(""))) { +// +// } catch (IOException e) { +// throw new RuntimeException(e); +// } + + // int x = 1/ 0; + int[] a = new int[1]; + System.out.println(a[2]); + } + +} diff --git a/src/main/java/hse/java/lectures/lecture3/practice/randomSet/EmptySetException.java b/src/main/java/hse/java/lectures/lecture3/practice/randomSet/EmptySetException.java new file mode 100644 index 00000000..c7820ac5 --- /dev/null +++ b/src/main/java/hse/java/lectures/lecture3/practice/randomSet/EmptySetException.java @@ -0,0 +1,7 @@ +package hse.java.lectures.lecture3.practice.randomSet; + +public class EmptySetException extends RuntimeException { + public EmptySetException(String message) { + super(message); + } +} diff --git a/src/main/java/hse/java/lectures/lecture3/practice/randomSet/RandomSet.java b/src/main/java/hse/java/lectures/lecture3/practice/randomSet/RandomSet.java new file mode 100644 index 00000000..8af477b5 --- /dev/null +++ b/src/main/java/hse/java/lectures/lecture3/practice/randomSet/RandomSet.java @@ -0,0 +1,21 @@ +package hse.java.lectures.lecture3.practice.randomSet; + +public class RandomSet { + + public boolean insert(T value) { + throw new UnsupportedOperationException("Not implemented"); + } + + public boolean remove(T value) { + throw new UnsupportedOperationException("Not implemented"); + } + + public boolean contains(T value) { + throw new UnsupportedOperationException("Not implemented"); + } + + public T getRandom() { + throw new UnsupportedOperationException("Not implemented"); + } + +} diff --git a/src/main/java/hse/java/lectures/lecture3/practice/randomSet/task.md b/src/main/java/hse/java/lectures/lecture3/practice/randomSet/task.md new file mode 100644 index 00000000..fb987780 --- /dev/null +++ b/src/main/java/hse/java/lectures/lecture3/practice/randomSet/task.md @@ -0,0 +1,52 @@ +# Случайное множество (RandomSet) + +Tag: randomset + +## Условие + +Реализовать класс `RandomSet`, который хранит множество целых чисел и поддерживает операции вставки, удаления и получения случайного элемента. + +## Требования к асимптотике + +- `insert(x)` — **O(log n) or O(1)** +- `remove(x)` — **O(log n) or O(1)** +- `contains(x)` — **(log n) or O(1)** +- `getRandom()` — **O(1)** + +Не допускается использование стандартных коллекций Java. + +## Публичный API + +1. `RandomSet()` — создаёт пустое множество. +1. `boolean insert(T value)` — добавляет `value` в множество. + - Возвращает `true`, если элемент добавлен. + - Возвращает `false`, если элемент уже был в множестве. +1. `boolean remove(T value)` — удаляет `value` из множества. + - Возвращает `true`, если элемент был удалён. + - Возвращает `false`, если элемента не было. +1. `boolean contains(T value)` — проверяет наличие `value` в множестве. + - Возвращает `true`, если элемент есть. + - Возвращает `false`, если элемента нет. +1. `T getRandom()` — возвращает случайный элемент из множества. + - Если множество пустое, выбрасывает `EmptySetException`. + +## Исключения + +- `EmptySetException` — попытка получить случайный элемент из пустого множества. + +## Пример + +```java +RandomSet set = new RandomSet<>(); +set.insert(10); // true +set.insert(20); // true +set.insert(10); // false + +int x = set.getRandom(); // 10 или 20 + +set.remove(10); // true +set.remove(10); // false + +set.contains(20); // true +set.contains(30); // false +``` diff --git a/src/main/java/hse/java/lectures/lecture3/tasks/atm/Atm.java b/src/main/java/hse/java/lectures/lecture3/tasks/atm/Atm.java index 3fa91909..08f551e4 100644 --- a/src/main/java/hse/java/lectures/lecture3/tasks/atm/Atm.java +++ b/src/main/java/hse/java/lectures/lecture3/tasks/atm/Atm.java @@ -1,14 +1,9 @@ package hse.java.lectures.lecture3.tasks.atm; -import java.util.Collections; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.TreeSet; +import java.util.*; public class Atm { - private enum Denomination { + public enum Denomination { D50(50), D100(100), D500(500), @@ -24,6 +19,12 @@ private enum Denomination { int value() { return value; } + + public static Denomination fromInt(int value) { + return Arrays.stream(values()).filter(v -> v.value == value) + .findFirst() + .orElse(null); + } } private final Map banknotes = new EnumMap<>(Denomination.class); @@ -31,9 +32,9 @@ int value() { public Atm() { } - public void deposit(Map banknotes){} + public void deposit(Map banknotes){} - public Map withdraw(int amount) { + public Map withdraw(int amount) { return Map.of(); } diff --git a/src/main/java/hse/java/lectures/lecture3/tasks/atm/task.md b/src/main/java/hse/java/lectures/lecture3/tasks/atm/task.md index 3845672e..6d574e19 100644 --- a/src/main/java/hse/java/lectures/lecture3/tasks/atm/task.md +++ b/src/main/java/hse/java/lectures/lecture3/tasks/atm/task.md @@ -1,5 +1,7 @@ # Банкомат (ATM) +Tag: atm + ## Условие Реализовать класс `Atm`, который хранит купюры фиксированных номиналов и выполняет операции пополнения и выдачи наличных. При ошибках операции должны завершаться выбросом исключения без изменения состояния банкомата. diff --git a/src/main/java/hse/java/lectures/lecture3/tasks/html/task.md b/src/main/java/hse/java/lectures/lecture3/tasks/html/task.md index 4ec71b18..2160175f 100644 --- a/src/main/java/hse/java/lectures/lecture3/tasks/html/task.md +++ b/src/main/java/hse/java/lectures/lecture3/tasks/html/task.md @@ -1,5 +1,7 @@ # Валидация HTML-документа +Tag: html + ## Условие Реализовать класс `HtmlDocument`, который читает HTML-текст из файла и проверяет корректность структуры. Если документ невалиден, при создании объекта выбрасывается исключение. diff --git a/src/main/java/hse/java/practice/task1/RubiksCube.java b/src/main/java/hse/java/practice/task1/RubiksCube.java index 2091b657..a05ec963 100644 --- a/src/main/java/hse/java/practice/task1/RubiksCube.java +++ b/src/main/java/hse/java/practice/task1/RubiksCube.java @@ -2,23 +2,12 @@ import java.util.Arrays; -/** - * Необходимо реализовать интерфейс Cube - * При повороте передней грани, меняются верх низ право и лево - */ -public class RubiksCube { +public class RubiksCube implements Cube { private static final int EDGES_COUNT = 6; private final Edge[] edges = new Edge[EDGES_COUNT]; - /** - * Создать валидный собранный кубик - * грани разместить по ордеру в енуме цветов - * грань 0 -> цвет 0 - * грань 1 -> цвет 1 - * ... - */ public RubiksCube() { CubeColor[] colors = CubeColor.values(); for (int i = 0; i < 6; i++) { @@ -26,10 +15,237 @@ public RubiksCube() { } } + private CubeColor[][] copyParts(CubeColor[][] otherParts) { + CubeColor[][] parts = new CubeColor[3][3]; + for (int i = 0; i < 3; i++) { + System.arraycopy(otherParts[i], 0, parts[i], 0, 3); + } + return parts; + } + + private void rotateInnerClockwise(EdgePosition position) { + CubeColor[][] parts = edges[position.ordinal()].getParts(); + CubeColor[][] partsCopy = new CubeColor[3][3]; + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + partsCopy[j][2-i] = parts[i][j]; + } + } + + edges[position.ordinal()].setParts(partsCopy); + } + + @Override + public void up(RotateDirection direction) { + switch (direction) { + case CLOCKWISE -> { + rotateUpClockwise(); + } + case COUNTERCLOCKWISE -> { + rotateUpClockwise(); + rotateUpClockwise(); + rotateUpClockwise(); + } + } + } + + private void rotateUpClockwise() { + rotateInnerClockwise(EdgePosition.UP); + + CubeColor[][] upParts = edges[5].getParts(); + CubeColor[][] leftParts = edges[2].getParts(); + CubeColor[][] rightParts = edges[3].getParts(); + CubeColor[][] downParts = edges[4].getParts(); + + CubeColor[][] upPartsCopy = copyParts(upParts); + CubeColor[][] leftPartsCopy = copyParts(leftParts); + CubeColor[][] rightPartsCopy = copyParts(rightParts); + CubeColor[][] downPartsCopy = copyParts(downParts); + + for (int i = 0; i < 3; i++) { + upParts[0][i] = leftPartsCopy[0][i]; + rightParts[0][i] = upPartsCopy[0][i]; + downParts[0][i] = rightPartsCopy[0][i]; + leftParts[0][i] = downPartsCopy[0][i]; + } + } + + @Override + public void down(RotateDirection direction) { + switch (direction) { + case CLOCKWISE -> { + rotateDownClockwise(); + } + case COUNTERCLOCKWISE -> { + rotateDownClockwise(); + rotateDownClockwise(); + rotateDownClockwise(); + } + } + + } + + private void rotateDownClockwise() { + rotateInnerClockwise(EdgePosition.DOWN); + + CubeColor[][] upParts = edges[4].getParts(); + CubeColor[][] leftParts = edges[2].getParts(); + CubeColor[][] rightParts = edges[3].getParts(); + CubeColor[][] downParts = edges[5].getParts(); + + CubeColor[][] upPartsCopy = copyParts(upParts); + CubeColor[][] leftPartsCopy = copyParts(leftParts); + CubeColor[][] rightPartsCopy = copyParts(rightParts); + CubeColor[][] downPartsCopy = copyParts(downParts); + + for (int i = 0; i < 3; i++) { + upParts[2][i] = leftPartsCopy[2][i]; + rightParts[2][i] = upPartsCopy[2][i]; + downParts[2][i] = rightPartsCopy[2][i]; + leftParts[2][i] = downPartsCopy[2][i]; + } + } + + @Override + public void left(RotateDirection direction) { + switch (direction) { + case CLOCKWISE -> { + rotateLeftClockwise(); + } + case COUNTERCLOCKWISE -> { + rotateLeftClockwise(); + rotateLeftClockwise(); + rotateLeftClockwise(); + } + } + } + + private void rotateLeftClockwise() { + rotateInnerClockwise(EdgePosition.LEFT); + + CubeColor[][] upParts = edges[0].getParts(); + CubeColor[][] leftParts = edges[5].getParts(); + CubeColor[][] rightParts = edges[4].getParts(); + CubeColor[][] downParts = edges[1].getParts(); + + CubeColor[][] upPartsCopy = copyParts(upParts); + CubeColor[][] leftPartsCopy = copyParts(leftParts); + CubeColor[][] rightPartsCopy = copyParts(rightParts); + CubeColor[][] downPartsCopy = copyParts(downParts); + + for (int i = 0; i < 3; i++) { + upParts[i][0] = leftPartsCopy[2-i][2]; + rightParts[i][0] = upPartsCopy[i][0]; + downParts[i][0] = rightPartsCopy[i][0]; + leftParts[i][2] = downPartsCopy[2-i][0]; + } + } + + @Override + public void right(RotateDirection direction) { + switch (direction) { + case CLOCKWISE -> { + rotateRightClockwise(); + } + case COUNTERCLOCKWISE -> { + rotateRightClockwise(); + rotateRightClockwise(); + rotateRightClockwise(); + } + } + } + + private void rotateRightClockwise() { + rotateInnerClockwise(EdgePosition.RIGHT); + + CubeColor[][] upParts = edges[0].getParts(); + CubeColor[][] leftParts = edges[4].getParts(); + CubeColor[][] rightParts = edges[5].getParts(); + CubeColor[][] downParts = edges[1].getParts(); + + CubeColor[][] upPartsCopy = copyParts(upParts); + CubeColor[][] leftPartsCopy = copyParts(leftParts); + CubeColor[][] rightPartsCopy = copyParts(rightParts); + CubeColor[][] downPartsCopy = copyParts(downParts); + + for (int i = 0; i < 3; i++) { + upParts[i][2] = leftPartsCopy[i][2]; + rightParts[i][0] = upPartsCopy[2-i][2]; + downParts[i][2] = rightPartsCopy[2-i][0]; + leftParts[i][2] = downPartsCopy[i][2]; + } + } + public void front(RotateDirection direction) { + switch (direction) { + case CLOCKWISE -> { + rotateFrontClockwise(); + } + case COUNTERCLOCKWISE -> { + rotateFrontClockwise(); + rotateFrontClockwise(); + rotateFrontClockwise(); + } + } + } + + private void rotateFrontClockwise() { + rotateInnerClockwise(EdgePosition.FRONT); + + CubeColor[][] upParts = edges[0].getParts(); + CubeColor[][] leftParts = edges[2].getParts(); + CubeColor[][] rightParts = edges[3].getParts(); + CubeColor[][] downParts = edges[1].getParts(); + + CubeColor[][] upPartsCopy = copyParts(upParts); + CubeColor[][] leftPartsCopy = copyParts(leftParts); + CubeColor[][] rightPartsCopy = copyParts(rightParts); + CubeColor[][] downPartsCopy = copyParts(downParts); + + for (int i = 0; i < 3; i++) { + upParts[2][i] = leftPartsCopy[2-i][2]; + rightParts[i][0] = upPartsCopy[2][i]; + downParts[0][i] = rightPartsCopy[2-i][0]; + leftParts[i][2] = downPartsCopy[0][i]; + } + } + + @Override + public void back(RotateDirection direction) { + switch (direction) { + case CLOCKWISE -> { + rotateBackClockwise(); + } + case COUNTERCLOCKWISE -> { + rotateBackClockwise(); + rotateBackClockwise(); + rotateBackClockwise(); + } + } + } + + private void rotateBackClockwise() { + rotateInnerClockwise(EdgePosition.BACK); + CubeColor[][] upParts = edges[0].getParts(); + CubeColor[][] leftParts = edges[3].getParts(); + CubeColor[][] rightParts = edges[2].getParts(); + CubeColor[][] downParts = edges[1].getParts(); + + CubeColor[][] upPartsCopy = copyParts(upParts); + CubeColor[][] leftPartsCopy = copyParts(leftParts); + CubeColor[][] rightPartsCopy = copyParts(rightParts); + CubeColor[][] downPartsCopy = copyParts(downParts); + + for (int i = 0; i < 3; i++) { + upParts[0][i] = leftPartsCopy[i][2]; + rightParts[i][0] = upPartsCopy[0][2-i]; + downParts[2][i] = rightPartsCopy[i][0]; + leftParts[i][2] = downPartsCopy[2][2-i]; + } } - + public Edge[] getEdges() { return edges; } @@ -38,4 +254,4 @@ public Edge[] getEdges() { public String toString() { return Arrays.toString(edges); } -} +} \ No newline at end of file diff --git a/src/main/java/hse/java/practice/task1/task.md b/src/main/java/hse/java/practice/task1/task.md index 235cc660..60dc4533 100644 --- a/src/main/java/hse/java/practice/task1/task.md +++ b/src/main/java/hse/java/practice/task1/task.md @@ -1,5 +1,7 @@ Необходимо реализовать методы поворота кубика Рубика. +Tag: cube + Кубик представляет собой классический куб 3×3×3: - 6 граней; - каждая грань состоит из 9 элементов (стикеров); diff --git a/src/test/java/hse/java/lectures/lecture3/practice/randomSet/RandomSetBaseTest.java b/src/test/java/hse/java/lectures/lecture3/practice/randomSet/RandomSetBaseTest.java new file mode 100644 index 00000000..37c38ab1 --- /dev/null +++ b/src/test/java/hse/java/lectures/lecture3/practice/randomSet/RandomSetBaseTest.java @@ -0,0 +1,28 @@ +package hse.java.lectures.lecture3.practice.randomSet; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +class RandomSetBaseTest { + + @Test + void insertRemoveContains() { + RandomSet set = new RandomSet<>(); + + assertTrue(set.insert(10)); + assertTrue(set.insert(20)); + assertFalse(set.insert(10)); + + assertTrue(set.contains(10)); + assertTrue(set.contains(20)); + assertFalse(set.contains(30)); + + assertTrue(set.remove(10)); + assertFalse(set.remove(10)); + + assertFalse(set.contains(10)); + assertTrue(set.contains(20)); + } + +} diff --git a/src/test/java/hse/java/lectures/lecture3/tasks/atm/AtmTest.java b/src/test/java/hse/java/lectures/lecture3/tasks/atm/AtmTest.java index 9e3994a6..5e83fcae 100644 --- a/src/test/java/hse/java/lectures/lecture3/tasks/atm/AtmTest.java +++ b/src/test/java/hse/java/lectures/lecture3/tasks/atm/AtmTest.java @@ -15,6 +15,7 @@ import java.util.Map; import java.util.stream.Stream; +import static hse.java.lectures.lecture3.tasks.atm.Atm.Denomination.*; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -30,18 +31,11 @@ void initialBalanceIsZero() { @Test void depositIncreasesBalance() { Atm atm = new Atm(); - atm.deposit(Map.of(100, 10, 500, 5)); + atm.deposit(Map.of(D100, 10, D500, 5)); assertEquals(3500, atm.getBalance()); } - @Test - void depositRejectsInvalidDenominationAndKeepsState() { - Atm atm = new Atm(); - atm.deposit(Map.of(100, 1)); - assertThrows(InvalidDepositException.class, () -> atm.deposit(Map.of(30, 1))); - assertEquals(100, atm.getBalance()); - } - + @Test void depositRejectsNullMap() { Atm atm = new Atm(); @@ -51,8 +45,8 @@ void depositRejectsNullMap() { @Test void depositRejectsNonPositiveCountAndKeepsState() { Atm atm = new Atm(); - atm.deposit(Map.of(100, 1)); - assertThrows(InvalidDepositException.class, () -> atm.deposit(Map.of(100, 0))); + atm.deposit(Map.of(D100, 1)); + assertThrows(InvalidDepositException.class, () -> atm.deposit(Map.of(D100, 0))); assertEquals(100, atm.getBalance()); } @@ -60,11 +54,11 @@ void depositRejectsNonPositiveCountAndKeepsState() { void withdrawGreedyAndUpdatesBalance() { Atm atm = new Atm(); // 2000 - atm.deposit(Map.of(1000, 1, 500, 1, 100, 5)); + atm.deposit(Map.of(D1000, 1, D500, 1, D100, 5)); - Map result = atm.withdraw(1700); + Map result = atm.withdraw(1700); - assertEquals(Map.of(1000, 1, 500, 1, 100, 2), result); + assertEquals(Map.of(D1000, 1, D500, 1, D100, 2), result); assertEquals(300, atm.getBalance()); } @@ -78,14 +72,14 @@ void withdrawRejectsInvalidAmount() { @Test void withdrawRejectsInsufficientFunds() { Atm atm = new Atm(); - atm.deposit(Map.of(100, 2)); + atm.deposit(Map.of(D100, 2)); assertThrows(InsufficientFundsException.class, () -> atm.withdraw(300)); } @Test void withdrawRejectsUnmakeableAmountAndKeepsState() { Atm atm = new Atm(); - atm.deposit(Map.of(500, 1, 100, 1)); + atm.deposit(Map.of(D500, 1, D100, 1)); assertThrows(CannotDispenseException.class, () -> atm.withdraw(150)); assertEquals(600, atm.getBalance()); } @@ -96,7 +90,7 @@ void additionalTests(AtmCase atmCase) { Atm atm = new Atm(); for (Map deposit : atmCase.deposits) { - atm.deposit(toIntMap(deposit)); + atm.deposit(toMap(deposit)); } if (atmCase.expect.exception != null) { @@ -105,8 +99,8 @@ void additionalTests(AtmCase atmCase) { "Case: " + atmCase.name); assertEquals(atmCase.expect.balance, atm.getBalance(), "Case: " + atmCase.name); } else { - Map result = atm.withdraw(atmCase.withdraw); - assertEquals(toIntMap(atmCase.expect.dispense), result, "Case: " + atmCase.name); + Map result = atm.withdraw(atmCase.withdraw); + assertEquals(toMap(atmCase.expect.dispense), result, "Case: " + atmCase.name); assertEquals(atmCase.expect.balance, atm.getBalance(), "Case: " + atmCase.name); } } @@ -126,10 +120,10 @@ private static List loadCases() throws IOException { } } - private Map toIntMap(Map source) { - Map result = new HashMap<>(); + private Map toMap(Map source) { + Map result = new HashMap<>(); for (Map.Entry entry : source.entrySet()) { - result.put(Integer.parseInt(entry.getKey()), entry.getValue()); + result.put(Atm.Denomination.fromInt(Integer.parseInt(entry.getKey())), entry.getValue()); } return result; } diff --git a/src/test/java/hse/java/practice/task1/CubeSimpleTest.java b/src/test/java/hse/java/practice/task1/CubeSimpleTest.java new file mode 100644 index 00000000..b13c4af7 --- /dev/null +++ b/src/test/java/hse/java/practice/task1/CubeSimpleTest.java @@ -0,0 +1,277 @@ +package hse.java.practice.task1; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; + +public class CubeSimpleTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Test + void printTest() { + RubiksCube cube = new RubiksCube(); + CubeColor[] colors = CubeColor.values(); + for (int i = 0; i < 6; i++) { + Edge edge = cube.getEdges()[i]; + CubeColor[][] edgeColors = edge.getParts(); + for (CubeColor[] row : edgeColors) { + for (CubeColor color : row) { + Assertions.assertEquals(color, colors[i]); + } + } + } + } + + @Test + void frontClockwise() { + RubiksCube cube = new RubiksCube(); + cube.front(RotateDirection.CLOCKWISE); + + CubeColor[][][] state = readStateFromFile("frontClockwieseState.json"); + + CubeColor[][][] actuallyState = Arrays.stream(cube.getEdges()) + .map(Edge::getParts) + .toArray(CubeColor[][][]::new); + + Assertions.assertArrayEquals(state, actuallyState); + } + + @Test + void frontCounterclockwise() { + RubiksCube cube = new RubiksCube(); + // Поворот против часовой стрелки = 3 поворота по часовой стрелке + cube.front(RotateDirection.COUNTERCLOCKWISE); + + // Проверяем, что 4 поворота против часовой возвращают в исходное состояние + cube.front(RotateDirection.COUNTERCLOCKWISE); + cube.front(RotateDirection.COUNTERCLOCKWISE); + cube.front(RotateDirection.COUNTERCLOCKWISE); + + CubeColor[] colors = CubeColor.values(); + for (int i = 0; i < 6; i++) { + Edge edge = cube.getEdges()[i]; + CubeColor[][] edgeColors = edge.getParts(); + for (CubeColor[] row : edgeColors) { + for (CubeColor color : row) { + Assertions.assertEquals(colors[i], color); + } + } + } + } + + @Test + void upClockwise() { + RubiksCube cube = new RubiksCube(); + cube.up(RotateDirection.CLOCKWISE); + + CubeColor[][][] state = readStateFromFile("upClockwiseState.json"); + + CubeColor[][][] actuallyState = Arrays.stream(cube.getEdges()) + .map(Edge::getParts) + .toArray(CubeColor[][][]::new); + + Assertions.assertArrayEquals(state, actuallyState); + } + + @Test + void upCounterclockwise() { + RubiksCube cube = new RubiksCube(); + cube.up(RotateDirection.COUNTERCLOCKWISE); + cube.up(RotateDirection.COUNTERCLOCKWISE); + cube.up(RotateDirection.COUNTERCLOCKWISE); + cube.up(RotateDirection.COUNTERCLOCKWISE); + + CubeColor[] colors = CubeColor.values(); + for (int i = 0; i < 6; i++) { + Edge edge = cube.getEdges()[i]; + CubeColor[][] edgeColors = edge.getParts(); + for (CubeColor[] row : edgeColors) { + for (CubeColor color : row) { + Assertions.assertEquals(colors[i], color); + } + } + } + } + + @Test + void downClockwise() { + RubiksCube cube = new RubiksCube(); + cube.down(RotateDirection.CLOCKWISE); + + CubeColor[][][] state = readStateFromFile("downClockwiseState.json"); + + CubeColor[][][] actuallyState = Arrays.stream(cube.getEdges()) + .map(Edge::getParts) + .toArray(CubeColor[][][]::new); + + Assertions.assertArrayEquals(state, actuallyState); + } + + @Test + void downCounterclockwise() { + RubiksCube cube = new RubiksCube(); + cube.down(RotateDirection.COUNTERCLOCKWISE); + cube.down(RotateDirection.COUNTERCLOCKWISE); + cube.down(RotateDirection.COUNTERCLOCKWISE); + cube.down(RotateDirection.COUNTERCLOCKWISE); + + CubeColor[] colors = CubeColor.values(); + for (int i = 0; i < 6; i++) { + Edge edge = cube.getEdges()[i]; + CubeColor[][] edgeColors = edge.getParts(); + for (CubeColor[] row : edgeColors) { + for (CubeColor color : row) { + Assertions.assertEquals(colors[i], color); + } + } + } + } + + @Test + void leftClockwise() { + RubiksCube cube = new RubiksCube(); + cube.left(RotateDirection.CLOCKWISE); + + CubeColor[][][] state = readStateFromFile("leftClockwiseState.json"); + + CubeColor[][][] actuallyState = Arrays.stream(cube.getEdges()) + .map(Edge::getParts) + .toArray(CubeColor[][][]::new); + + Assertions.assertArrayEquals(state, actuallyState); + } + + @Test + void leftCounterclockwise() { + RubiksCube cube = new RubiksCube(); + cube.left(RotateDirection.COUNTERCLOCKWISE); + cube.left(RotateDirection.COUNTERCLOCKWISE); + cube.left(RotateDirection.COUNTERCLOCKWISE); + cube.left(RotateDirection.COUNTERCLOCKWISE); + + CubeColor[] colors = CubeColor.values(); + for (int i = 0; i < 6; i++) { + Edge edge = cube.getEdges()[i]; + CubeColor[][] edgeColors = edge.getParts(); + for (CubeColor[] row : edgeColors) { + for (CubeColor color : row) { + Assertions.assertEquals(colors[i], color); + } + } + } + } + + @Test + void rightClockwise() { + RubiksCube cube = new RubiksCube(); + cube.right(RotateDirection.CLOCKWISE); + + CubeColor[][][] state = readStateFromFile("rightClockwiseState.json"); + + CubeColor[][][] actuallyState = Arrays.stream(cube.getEdges()) + .map(Edge::getParts) + .toArray(CubeColor[][][]::new); + + Assertions.assertArrayEquals(state, actuallyState); + } + + @Test + void rightCounterclockwise() { + RubiksCube cube = new RubiksCube(); + cube.right(RotateDirection.COUNTERCLOCKWISE); + cube.right(RotateDirection.COUNTERCLOCKWISE); + cube.right(RotateDirection.COUNTERCLOCKWISE); + cube.right(RotateDirection.COUNTERCLOCKWISE); + + CubeColor[] colors = CubeColor.values(); + for (int i = 0; i < 6; i++) { + Edge edge = cube.getEdges()[i]; + CubeColor[][] edgeColors = edge.getParts(); + for (CubeColor[] row : edgeColors) { + for (CubeColor color : row) { + Assertions.assertEquals(colors[i], color); + } + } + } + } + + @Test + void backClockwise() { + RubiksCube cube = new RubiksCube(); + cube.back(RotateDirection.CLOCKWISE); + + CubeColor[][][] state = readStateFromFile("backClockwiseState.json"); + + CubeColor[][][] actuallyState = Arrays.stream(cube.getEdges()) + .map(Edge::getParts) + .toArray(CubeColor[][][]::new); + + Assertions.assertArrayEquals(state, actuallyState); + } + + @Test + void backCounterclockwise() { + RubiksCube cube = new RubiksCube(); + cube.back(RotateDirection.COUNTERCLOCKWISE); + cube.back(RotateDirection.COUNTERCLOCKWISE); + cube.back(RotateDirection.COUNTERCLOCKWISE); + cube.back(RotateDirection.COUNTERCLOCKWISE); + + CubeColor[] colors = CubeColor.values(); + for (int i = 0; i < 6; i++) { + Edge edge = cube.getEdges()[i]; + CubeColor[][] edgeColors = edge.getParts(); + for (CubeColor[] row : edgeColors) { + for (CubeColor color : row) { + Assertions.assertEquals(colors[i], color); + } + } + } + } + + @Test + void combinedRotations() { + RubiksCube cube = new RubiksCube(); + + // Выполняем последовательность поворотов + cube.front(RotateDirection.CLOCKWISE); + cube.right(RotateDirection.CLOCKWISE); + cube.up(RotateDirection.CLOCKWISE); + + // Выполняем обратную последовательность + cube.up(RotateDirection.COUNTERCLOCKWISE); + cube.right(RotateDirection.COUNTERCLOCKWISE); + cube.front(RotateDirection.COUNTERCLOCKWISE); + + // Должны вернуться в исходное состояние + CubeColor[] colors = CubeColor.values(); + for (int i = 0; i < 6; i++) { + Edge edge = cube.getEdges()[i]; + CubeColor[][] edgeColors = edge.getParts(); + for (CubeColor[] row : edgeColors) { + for (CubeColor color : row) { + Assertions.assertEquals(colors[i], color); + } + } + } + } + + private CubeColor[][][] readStateFromFile(String fileName) { + String resourcePath = "hse/java/practice/task1/" + fileName; + try (InputStream is = CubeSimpleTest.class.getClassLoader().getResourceAsStream(resourcePath)) { + if (is == null) { + throw new IllegalArgumentException("Resource not found: " + resourcePath); + } + String json = new String(is.readAllBytes(), StandardCharsets.UTF_8); + return MAPPER.readValue(json, CubeColor[][][].class); + } catch (IOException e) { + throw new RuntimeException("Failed to read/parse state file: " + fileName, e); + } + } +} diff --git a/src/test/test.iml b/src/test/test.iml new file mode 100644 index 00000000..2185ddd0 --- /dev/null +++ b/src/test/test.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file