Skip to content

Commit

Permalink
pass context tests
Browse files Browse the repository at this point in the history
  • Loading branch information
fengmk2 committed Dec 23, 2024
1 parent a850fc2 commit 4c4d584
Show file tree
Hide file tree
Showing 23 changed files with 1,585 additions and 1,517 deletions.
2,536 changes: 1,268 additions & 1,268 deletions index-old.d.ts

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,15 @@
"coffee": "5",
"cross-env": "7",
"egg-bin": "beta",
"egg-mock": "beta",
"@eggjs/mock": "beta",
"egg-plugin-puml": "^2.4.0",
"egg-tracer": "^2.1.0",
"egg-view-nunjucks": "^2.3.0",
"eslint": "8",
"eslint-config-egg": "14",
"formstream": "^1.5.1",
"koa-static": "^5.0.0",
"mm": "^3.4.0",
"pedding": "^1.1.0",
"prettier": "^2.7.1",
"runscript": "^2.0.1",
Expand Down Expand Up @@ -107,6 +108,9 @@
"url": "git://github.com/eggjs/egg.git"
},
"license": "MIT",
"tnpm": {
"mode": "npm"
},
"egg": {
"framework": true,
"exports": {
Expand Down
62 changes: 35 additions & 27 deletions site/docs/advanced/framework.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ order: 3
If your team have met with these scenarios:

- Each project contains the same configuration files that need to be copied every time, such as `gulpfile.js`, `webpack.config.js`.
- Each project has similiar dependencies.
- Each project has similar dependencies.
- It's difficult to synchronize those projects based on the same configurations like those mentioned above once they have been optimized?

If your team needs:
Expand All @@ -16,7 +16,7 @@ If your team needs:
- a unified [deployment plan](../core/deployment.md) keeping developers concentrate on code without paying attention to deployment details of connecting the framework and platforms.
- a unified code style to decrease code's repetition and optimize code's appearance, which is important for a enterprise level framework.

To satisfy these demands, Egg endows developers with the capacity of `customazing a framework`. It is just an abstract layer, which can be constructed to a higher level framework, supporting inheritance of unlimited times. Futhermore, Egg apply a quantity of coding conventions based on Koa.
To satisfy these demands, Egg endows developers with the capacity of `customizing a framework`. It is just an abstract layer, which can be constructed to a higher level framework, supporting inheritance of unlimited times. Furthermore, Egg apply a quantity of coding conventions based on Koa.

Therefore, a uniform spec can be applied on projects in which the differentiation fulfilled in plugins. And the best practice summed from those projects can be continuously extracted from these plugins to the framework, which is available to other projects by just updating the dependencies' versions.

Expand All @@ -30,7 +30,7 @@ They both are inherited from [EggCore](https://github.com/eggjs/egg-core), and A

We could regard EggCore as the advanced version of Koa Application, which integrates built-in features such as [Loader](./loader.md)[Router](../basics/router.md) and asynchronous launch.

```
```bash
Koa Application
^
EggCore
Expand Down Expand Up @@ -92,7 +92,7 @@ To customize framework, Loader is required and has to be inherited from Egg Load

If we consider a framework as a class, then Egg framework is the base class,and implementing a framework demands to implement entire APIs of Egg.

```js
```bash
// package.json
{
"name": "yadan",
Expand Down Expand Up @@ -122,7 +122,7 @@ module.exports = Object.assign(egg, {
});
```
The name of framework, default as `egg`, is a indispensable option to launch an application, set by `egg.framwork` of `package.json`, then Loader loads the exported app of a module named it.
The name of framework, default as `egg`, is a indispensable option to launch an application, set by `egg.framework` of `package.json`, then Loader loads the exported app of a module named it.
```json
{
Expand All @@ -139,7 +139,7 @@ As a loadUnit of framework, yadan is going to load specific directories and file
### Principle of Framework Extension
The path of framework is set as a varible named as `Symbol.for('egg#eggPath')` to expose itself to Loader. Why? It seems that the simplest way is to pass a param to the constructor. The reason is to expose those paths of each level of inherited frameworks and reserve their sequences. Since Egg is a framework capable of unlimited inheritance, each layer has to designate their own eggPath so that all the eggPaths are accessiable through the prototype chain.
The path of framework is set as a variable named as `Symbol.for('egg#eggPath')` to expose itself to Loader. Why? It seems that the simplest way is to pass a param to the constructor. The reason is to expose those paths of each level of inherited frameworks and reserve their sequences. Since Egg is a framework capable of unlimited inheritance, each layer has to designate their own eggPath so that all the eggPaths are accessible through the prototype chain.
Given a triple-layer framework: department level > enterprise level > Egg
Expand Down Expand Up @@ -173,7 +173,7 @@ These code are pseudocode to elaborate the framework's loading process, and we h

### Custom Agent

Egg's mutilprocess model is composed of Application and Agent. Therefore Agent, another fundamental class similiar to Application, is also required to be implemented.
Egg's multiprocess model is composed of Application and Agent. Therefore Agent, another fundamental class similar to Application, is also required to be implemented.

```js
// lib/framework.js
Expand Down Expand Up @@ -207,12 +207,13 @@ module.exports = Object.assign(egg, {

Loader, the core of the launch process, is capable of loading data code, adjusting loading orders or even strengthen regulation of code.

As the same as Egg-Path, Loader exposes itself at `Symbol.for('egg#loader')` to ensuer it's accessibility on prototype chain.
As the same as Egg-Path, Loader exposes itself at `Symbol.for('egg#loader')` to ensure it's accessibility on prototype chain.

```js
// lib/framework.js
const path = require('path');
const egg = require('egg');

const EGG_PATH = Symbol.for('egg#eggPath');

class YadanAppWorkerLoader extends egg.AppWorkerLoader {
Expand All @@ -236,39 +237,39 @@ class Application extends egg.Application {
// rewrite Egg's Application
module.exports = Object.assign(egg, {
Application,
// custom Loader, a dependence of the high level frameword, needs to be exported.
// custom Loader, a dependence of the high level framework, needs to be exported.
AppWorkerLoader: YadanAppWorkerLoader,
});
```

AgentworkerLoader is not going to be described because of it's similarity of AppWorkerLoader, but be aware of it's located at `agent.js` instand of `app.js`.
AgentWorkerLoader is not going to be described because of it's similarity of AppWorkerLoader, but be aware of it's located at `agent.js` instead of `app.js`.

## The principle of Launch

Many descriptions of launch process are scattered at [Mutilprocess Model](../core/cluster-and-ipc.md), [Loader](./loader.md) and [Plugin](./plugin.md), and here is a summarization.
Many descriptions of launch process are scattered at [Multiprocess Model](../core/cluster-and-ipc.md), [Loader](./loader.md) and [Plugin](./plugin.md), and here is a summarization.

- `startCluster` is invoked with `baseDir` and `framework`, then Master process is launched.
- Master forks a new process as Agent Worker
- instantiate Agent Class of the framework loaded from path passed by the `framework` param.
- Agent finds out the AgentWorkerLoader and then starts to load
- use AgentWorkerLoader to load Worker synchronously in the sequence of Plugin Config, Extend, `agent.js` and other files.
- The initiation of `agent.js` is able to be customized, and it supports asynchronous launch after which it notifies Master and invoke the function passed to `beforeStart`.
- After recieving the message that Agent Worker is launched,Master forks App Workers by cluster.
- App Workers are mutilple identical processes launched simultaneously
- App Worker is instantiated, which is similiar to Agent inherited Application class of framework loaded from framework path.
- After receiving the message that Agent Worker is launched,Master forks App Workers by cluster.
- App Workers are multiple identical processes launched simultaneously
- App Worker is instantiated, which is similar to Agent inherited Application class of framework loaded from framework path.
- The same as Agent, Loading process of Application starts with AppWorkerLoader which loads files in the same order and finally informed Master.
- After informed of luanching successfully of each App Worker, Master is finally functioning.
- After informed of launching successfully of each App Worker, Master is finally functioning.

## Framework Testing

You'd better read [unittest](../core/unittest.md) first, which is similiar to framework testing in a quantity of situations.
You'd better read [unittest](../core/unittest.md) first, which is similar to framework testing in a quantity of situations.

### Initiation

Here are some differences between initiation of frameworks.

```js
const mock = require('egg-mock');
const mock = require('@eggjs/mock');
describe('test/index.test.js', () => {
let app;
before(() => {
Expand All @@ -293,15 +294,16 @@ describe('test/index.test.js', () => {
- Different from application testing, framework testing tests framework code instead of application code, so that baseDir varies for the propose of testing kinds of applications.
- BaseDir is potentially considered to be under the path of `test/fixtures`, otherwise it should be absolute paths.
- The `framework` option is indispensable, which could be a absolute path or `true` meaning the path of the framework to be current directory.
- The use of the app should wait for the `ready` event in `before` hook, or some of the APIs is not avaiable.
- The use of the app should wait for the `ready` event in `before` hook, or some of the APIs is not available.
- Do not forget to invoke `app.close()` after testing, which could arouse the exhausting of fds, caused by unclosed log files.

### Cache

`mm.app` enables cache as default, which means new envoriment setting would not work once loaded.
`mm.app` enables cache as default, which means new environment setting would not work once loaded.

```js
const mock = require('egg-mock');
const mock = require('@eggjs/mock');

describe('/test/index.test.js', () => {
let app;
afterEach(() => app.close());
Expand All @@ -327,15 +329,16 @@ describe('/test/index.test.js', () => {
});
```

### Multipleprocess Testing
### Multiprocess Testing

Mutilprocess is rarely tested because of the high cost and the unavailability of API level's mock, meanwhile, processes have a slow start or even timeout, but it still remains the most effective way of testing multiprocess model.
Multiprocess is rarely tested because of the high cost and the unavailability of API level's mock, meanwhile, processes have a slow start or even timeout, but it still remains the most effective way of testing multiprocess model.

The option of `mock.cluster` have no difference with `mm.app` while their APIs are totally distinct, however, SuperTest still works.

```js
const mock = require('egg-mock');
describe('/test/index.test.js', () => {
const mock = require('@eggjs/mock');

describe('test/index.test.js', () => {
let app;
before(() => {
app = mock.cluster({
Expand All @@ -346,16 +349,20 @@ describe('/test/index.test.js', () => {
});
after(() => app.close());
afterEach(mock.restore);

it('should success', () => {
return app.httpRequest().get('/').expect(200);
return app.httpRequest()
.get('/')
.expect(200);
});
});
```

Tests of `stdout/stderr` are also avaiable, since `mm.cluster` is based on [coffee](https://github.com/popomore/coffee) in which multiprocess testing is supported.
Tests of `stdout/stderr` are also available, since `mm.cluster` is based on [coffee](https://github.com/popomore/coffee) in which multiprocess testing is supported.

```js
const mock = require('egg-mock');
const mock = require('@eggjs/mock');

describe('/test/index.test.js', () => {
let app;
before(() => {
Expand All @@ -366,6 +373,7 @@ describe('/test/index.test.js', () => {
return app.ready();
});
after(() => app.close());

it('should get `started`', () => {
// set the expectation of console
app.expect('stdout', /started/);
Expand Down
25 changes: 17 additions & 8 deletions site/docs/advanced/framework.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,7 @@ AgentWorkerLoader 的扩展也类似,这里不再赘述。AgentWorkerLoader
- 单个 App Worker 通过 framework 找到框架目录,实例化该框架的 Application 类。
- Application 根据 AppWorkerLoader 开始加载,加载顺序类似,会异步等待完成后通知 Master 启动完成。
4. Master 在等到所有 App Worker 发来的启动成功消息后,完成启动,开始对外提供服务。

## 框架测试

在看下文之前,请先查看[单元测试章节](../core/unittest.md)。框架测试的大部分使用场景和应用类似。
Expand All @@ -268,7 +269,8 @@ AgentWorkerLoader 的扩展也类似,这里不再赘述。AgentWorkerLoader
框架的初始化方式有一定差异。

```js
const mock = require('egg-mock');
const mock = require('@eggjs/mock');

describe('test/index.test.js', () => {
let app;
before(() => {
Expand Down Expand Up @@ -301,8 +303,9 @@ describe('test/index.test.js', () => {
在测试多环境场景需要使用到 cache 参数,因为 `mock.app` 默认有缓存,当第一次加载后再次加载会直接读取缓存,那么设置的环境也不会生效。

```js
const mock = require('egg-mock');
describe('/test/index.test.js', () => {
const mock = require('@eggjs/mock');

describe('test/index.test.js', () => {
let app;
afterEach(() => app.close());

Expand Down Expand Up @@ -334,8 +337,9 @@ describe('/test/index.test.js', () => {
多进程测试和 `mock.app` 参数一致,但 app 的 API 完全不同。不过,SuperTest 依然可用。

```js
const mock = require('egg-mock');
describe('/test/index.test.js', () => {
const mock = require('@eggjs/mock');

describe('test/index.test.js', () => {
let app;
before(() => {
app = mock.cluster({
Expand All @@ -346,17 +350,21 @@ describe('/test/index.test.js', () => {
});
after(() => app.close());
afterEach(mock.restore);

it('should success', () => {
return app.httpRequest().get('/').expect(200);
return app.httpRequest()
.get('/')
.expect(200);
});
});
```

多进程测试还可以测试 stdout/stderr,因为 `mock.cluster` 是基于 [coffee](https://github.com/popomore/coffee) 扩展的,可进行进程测试。

```js
const mock = require('egg-mock');
describe('/test/index.test.js', () => {
const mock = require('@eggjs/mock');

describe('test/index.test.js', () => {
let app;
before(() => {
app = mock.cluster({
Expand All @@ -366,6 +374,7 @@ describe('/test/index.test.js', () => {
return app.ready();
});
after(() => app.close());

it('should get `started`', () => {
// 判断终端输出
app.expect('stdout', /started/);
Expand Down
2 changes: 1 addition & 1 deletion site/docs/basics/schedule.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ There are some scenarios we may need to manually execute scheduled tasks, for ex
- Executing scheduled tasks manually for more elegant unit testing of scheduled tasks.

```js
const mm = require('egg-mock');
const mm = require('@eggjs/mock');
const assert = require('assert');

it('should schedule work fine', async () => {
Expand Down
4 changes: 2 additions & 2 deletions site/docs/basics/schedule.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ module.exports = (app) => {
- 手动执行定时任务可以更优雅地编写定时任务的单元测试。

```js
const mm = require('egg-mock');
const mm = require('@eggjs/mock');
const assert = require('assert');

it('should schedule work fine', async () => {
Expand Down Expand Up @@ -218,4 +218,4 @@ module.exports = (agent) => {

- `this.schedule` - 定时任务的属性,所有任务默认支持的 `disable` 属性,以及其他自定义配置的解析。
- `this.sendOne(...args)` - 随机通知某个 worker 执行 task,`args` 会传递给 `subscribe(...args)``task(ctx, ...args)` 方法。
- `this.sendAll(...args)` - 通知所有的 worker 执行 task。
- `this.sendAll(...args)` - 通知所有的 worker 执行 task。
2 changes: 1 addition & 1 deletion site/docs/core/unittest.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ We can easily create an app instance with Mocha's `before` hook through egg-mock
```js
// test/controller/home.test.js
const assert = require('assert');
const mock = require('egg-mock');
const mock = require('@eggjs/mock');

describe('test/controller/home.test.js', () => {
let app;
Expand Down
2 changes: 1 addition & 1 deletion site/docs/core/unittest.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ npm test
```javascript
// test/controller/home.test.js
const assert = require('assert');
const mock = require('egg-mock');
const mock = require('@eggjs/mock');

describe('test/controller/home.test.js', () => {
let app;
Expand Down
4 changes: 2 additions & 2 deletions src/lib/core/messenger/IMessenger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ export interface IMessenger extends EventEmitter {

/**
* send message to the specified process
* @param {String} pid - the process id of the receiver
* @param {String} workerId - the workerId of the receiver
* @param {String} action - message key
* @param {Object} data - message value
* @return {Messenger} this
*/
sendTo(pid: string, action: string, data?: unknown): IMessenger;
sendTo(workerId: string, action: string, data?: unknown): IMessenger;

/**
* send message to one app worker by random
Expand Down
2 changes: 1 addition & 1 deletion src/lib/core/messenger/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,5 @@ export type { IMessenger } from './IMessenger.js';
export function create(egg: EggApplicationCore): IMessenger {
return egg.options.mode === 'single'
? new LocalMessenger(egg)
: new IPCMessenger();
: new IPCMessenger(egg);
}
Loading

0 comments on commit 4c4d584

Please sign in to comment.