Skip to content
This repository was archived by the owner on Jun 3, 2025. It is now read-only.

Commit 92a5fc0

Browse files
author
mikkomaa
authored
Merge pull request #345 from tmc-cli/architecture-doc-update-aleksi
Improve the architecture document.
2 parents ce5b5b3 + d975624 commit 92a5fc0

File tree

1 file changed

+94
-13
lines changed

1 file changed

+94
-13
lines changed

docs/HACKING.md

Lines changed: 94 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,117 @@
1-
Hacking the tmc-cli
2-
===================
1+
Architecture and Hacking the tmc-cli
2+
====================================
33

44
## Requirements for developing tmc-cli
5-
* java jdk 7
5+
* jdk 7
66
* linux bash (mac's bash won't work)
77

88

9-
## Building a tmc-cli
10-
To build tmc-cli run the appropriate maven command.
9+
## Building tmc-cli
10+
To build tmc-cli run the appropriate Maven command.
1111

1212
$ git clone https://github.com/tmc-cli/tmc-cli.git
1313
$ mvn clean install
1414

15+
## Architecture
16+
17+
Program architecture is based on command design pattern. The program runs only a single command
18+
on each program execution.
19+
20+
Conceptually, there are three main parts in the program; the launch code, backend related code
21+
and the commands. The commands are gathered into their own package. In addition, there are utility
22+
classes in order to take care of user input/output and file handling.
23+
24+
The tmc-cli itself is mostly just an command line interface for the backend libraries.
25+
All the heavy lifting is done by [tmc-core](https://github.com/testmycode/tmc-core) and
26+
[tmc-langs](https://github.com/testmycode/tmc-langs) libraries. The tmc-cli also requires the
27+
server-side component of TestMyCode aka. [tmc-server](https://github.com/testmycode/tmc-server).
28+
29+
### Important classes
30+
31+
The `CliContext` object contains some cached data and singleton objects that are commonly used
32+
by utility classes and commands. Most importantly it has the `Io` object which is responsible
33+
for printing messages for the user and helping with other user interactions.
34+
35+
The `WorkDir` object handles most of the directory path handling. And it's used by most
36+
commands to parse the exercise arguments.
37+
38+
The `TmcUtil` class has wraps all tmc-cores functions into nicer interfac.
39+
1540
## Creating new commands
1641

17-
Every command must have `@Command` annotation and it's highly recomended to extend the AbstractCommand. Note that the `@Command` annotation is used for creating help messages and it's name field is used as the sub-command name in terminal.
42+
Every command must have `@Command` annotation and it's highly recommended to extend the
43+
AbstractCommand class. Note that the `@Command` annotation is used for creating help messages
44+
and its 'name' field is used as the sub-command name in terminal.
1845

19-
Please put the new commands into the `command` package.
46+
Please create all new commands inside the `command` package.
2047

2148
```java
22-
@Command(name = "command-name", desc = "Description of command")
49+
@Command(name = "command-name", desc = "Command description goes here")
2350
public class ExampleCommand extends AbstractCommand {
2451
@Override
2552
public void getOptions(Options options) {
26-
options.addOption("l", "long-option", false, "This will be seen in help message.");
53+
options.addOption("l", "long-option", false, "This will be seen in the help message.");
2754
// ...
2855
}
2956
}
3057
```
3158

32-
## Architecture
33-
...
59+
## Logging error messages
60+
61+
In case of failure, please print debug info in the error log by using slf4j logger and print some
62+
useful error messages to user with `ctx.getIo().println(' ... ');`. Ctx context object is
63+
passed into most of the code in tmc-cli and you can use it to interact with the user.
64+
65+
## Unit testing
66+
67+
If you create a new command, please use integration tests only. If you want to verify that a command
68+
or a utility class has printed text into the terminal, use `io.assertContains()` method. This custom
69+
assert method prints easily understandable error messages when it fails and doesn't require much code.
70+
71+
```java
72+
@RunWith(PowerMockRunner.class)
73+
@PrepareForTest(TmcUtil.class)
74+
public class ExampleCommandTest {
75+
76+
private Application app;
77+
private CliContext ctx;
78+
private TestIo io;
79+
private TmcCore mockCore;
80+
private WorkDir workDir;
81+
private Path tempDir;
82+
83+
@Before
84+
public void setUp() {
85+
// redirect the course related files into tmp
86+
tempDir = Paths.get(System.getProperty("java.io.tmpdir")).resolve("exampleTest");
87+
workDir = new WorkDir(tempDir);
88+
89+
io = new TestIo();
90+
mockCore = mock(TmcCore.class);// make sure that nothing is leaked to tmcCore
91+
ctx = new CliContext(io, workDir, mockCore);
92+
app = new Application(ctx);
93+
94+
mockStatic(TmcUtil.class);
95+
}
96+
97+
@After
98+
public void tearDown() {
99+
try {
100+
FileUtils.deleteDirectory(tempDir.toFile());
101+
} catch (Exception e) { }
102+
}
103+
104+
@Test
105+
public void exampleSucceedsWhenGivenValidCourse() {
106+
course = ...;
107+
when(TmcUtil.findCourse(eq(ctx), eq("course1"))).thenReturn(course);
108+
109+
String[] args = {"example course1"};
110+
app.run(args);
111+
io.assertContains("something went right");
112+
}
113+
}
114+
```
34115

35-
## Debugging
36-
### Logging
116+
If you are doing tests for any other class, simply create normal unit tests
117+
that don't depend on any command.

0 commit comments

Comments
 (0)