api-content-tracing-spec.ts 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import { app, contentTracing, TraceConfig, TraceCategoriesAndOptions } from 'electron/main';
  2. import { expect } from 'chai';
  3. import * as fs from 'node:fs';
  4. import * as path from 'node:path';
  5. import { setTimeout } from 'node:timers/promises';
  6. import { ifdescribe } from './lib/spec-helpers';
  7. // FIXME: The tests are skipped on linux arm/arm64
  8. ifdescribe(!(['arm', 'arm64'].includes(process.arch)) || (process.platform !== 'linux'))('contentTracing', () => {
  9. const record = async (options: TraceConfig | TraceCategoriesAndOptions, outputFilePath: string | undefined, recordTimeInMilliseconds = 1e1) => {
  10. await app.whenReady();
  11. await contentTracing.startRecording(options);
  12. await setTimeout(recordTimeInMilliseconds);
  13. const resultFilePath = await contentTracing.stopRecording(outputFilePath);
  14. return resultFilePath;
  15. };
  16. const outputFilePath = path.join(app.getPath('temp'), 'trace.json');
  17. beforeEach(() => {
  18. if (fs.existsSync(outputFilePath)) {
  19. fs.unlinkSync(outputFilePath);
  20. }
  21. });
  22. describe('startRecording', function () {
  23. if (process.platform === 'win32' && process.arch === 'arm64') {
  24. // WOA needs more time
  25. this.timeout(10e3);
  26. } else {
  27. this.timeout(5e3);
  28. }
  29. const getFileSizeInKiloBytes = (filePath: string) => {
  30. const stats = fs.statSync(filePath);
  31. const fileSizeInBytes = stats.size;
  32. const fileSizeInKiloBytes = fileSizeInBytes / 1024;
  33. return fileSizeInKiloBytes;
  34. };
  35. it('accepts an empty config', async () => {
  36. const config = {};
  37. await record(config, outputFilePath);
  38. expect(fs.existsSync(outputFilePath)).to.be.true('output exists');
  39. const fileSizeInKiloBytes = getFileSizeInKiloBytes(outputFilePath);
  40. expect(fileSizeInKiloBytes).to.be.above(0,
  41. `the trace output file is empty, check "${outputFilePath}"`);
  42. });
  43. it('accepts a trace config', async () => {
  44. // (alexeykuzmin): All categories are excluded on purpose,
  45. // so only metadata gets into the output file.
  46. const config = {
  47. excluded_categories: ['*']
  48. };
  49. await record(config, outputFilePath);
  50. // If the `excluded_categories` param above is not respected, categories
  51. // like `node,node.environment` will be included in the output.
  52. const content = fs.readFileSync(outputFilePath).toString();
  53. expect(content.includes('"cat":"node,node.environment"')).to.be.false();
  54. });
  55. it('accepts "categoryFilter" and "traceOptions" as a config', async () => {
  56. // (alexeykuzmin): All categories are excluded on purpose,
  57. // so only metadata gets into the output file.
  58. const config = {
  59. categoryFilter: '__ThisIsANonexistentCategory__',
  60. traceOptions: ''
  61. };
  62. await record(config, outputFilePath);
  63. expect(fs.existsSync(outputFilePath)).to.be.true('output exists');
  64. // If the `categoryFilter` param above is not respected
  65. // the file size will be above 50KB.
  66. const fileSizeInKiloBytes = getFileSizeInKiloBytes(outputFilePath);
  67. const expectedMaximumFileSize = 50; // Depends on a platform.
  68. expect(fileSizeInKiloBytes).to.be.above(0,
  69. `the trace output file is empty, check "${outputFilePath}"`);
  70. expect(fileSizeInKiloBytes).to.be.below(expectedMaximumFileSize,
  71. `the trace output file is suspiciously large (${fileSizeInKiloBytes}KB),
  72. check "${outputFilePath}"`);
  73. });
  74. });
  75. describe('stopRecording', function () {
  76. if (process.platform === 'win32' && process.arch === 'arm64') {
  77. // WOA needs more time
  78. this.timeout(10e3);
  79. } else {
  80. this.timeout(5e3);
  81. }
  82. it('does not crash on empty string', async () => {
  83. const options = {
  84. categoryFilter: '*',
  85. traceOptions: 'record-until-full,enable-sampling'
  86. };
  87. await contentTracing.startRecording(options);
  88. const path = await contentTracing.stopRecording('');
  89. expect(path).to.be.a('string').that.is.not.empty('result path');
  90. expect(fs.statSync(path).isFile()).to.be.true('output exists');
  91. });
  92. it('calls its callback with a result file path', async () => {
  93. const resultFilePath = await record(/* options */ {}, outputFilePath);
  94. expect(resultFilePath).to.be.a('string').and.be.equal(outputFilePath);
  95. });
  96. it('creates a temporary file when an empty string is passed', async function () {
  97. const resultFilePath = await record(/* options */ {}, /* outputFilePath */ '');
  98. expect(resultFilePath).to.be.a('string').that.is.not.empty('result path');
  99. });
  100. it('creates a temporary file when no path is passed', async function () {
  101. const resultFilePath = await record(/* options */ {}, /* outputFilePath */ undefined);
  102. expect(resultFilePath).to.be.a('string').that.is.not.empty('result path');
  103. });
  104. it('rejects if no trace is happening', async () => {
  105. await expect(contentTracing.stopRecording()).to.be.rejectedWith('Failed to stop tracing - no trace in progress');
  106. });
  107. });
  108. describe('captured events', () => {
  109. it('include V8 samples from the main process', async function () {
  110. this.timeout(60000);
  111. await contentTracing.startRecording({
  112. categoryFilter: 'disabled-by-default-v8.cpu_profiler',
  113. traceOptions: 'record-until-full'
  114. });
  115. {
  116. const start = Date.now();
  117. let n = 0;
  118. const f = () => {};
  119. while (Date.now() - start < 200 && n < 500) {
  120. await setTimeout(0);
  121. f();
  122. n++;
  123. }
  124. }
  125. const path = await contentTracing.stopRecording();
  126. const data = fs.readFileSync(path, 'utf8');
  127. const parsed = JSON.parse(data);
  128. expect(parsed.traceEvents.some((x: any) => x.cat === 'disabled-by-default-v8.cpu_profiler' && x.name === 'ProfileChunk')).to.be.true();
  129. });
  130. });
  131. });