StrictDoc Documentation
tests/unit/strictdoc/backend/sdoc_source_code/readers/test_reader_c.py
Source file coverage
Path:
tests/unit/strictdoc/backend/sdoc_source_code/readers/test_reader_c.py
Lines:
657
Non-empty lines:
522
Non-empty lines covered with requirements:
522 / 522 (100.0%)
Functions:
18
Functions covered by requirements:
18 / 18 (100.0%)
1
"""
2
@relation(SDOC-SRS-146, scope=file)
3
"""
4
 
5
from strictdoc.backend.sdoc_source_code.models.source_file_info import (
6
    SourceFileTraceabilityInfo,
7
)
8
from strictdoc.backend.sdoc_source_code.reader_c import (
9
    SourceFileTraceabilityReader_C,
10
)
11
 
12
 
13
def test_00_empty_file():
14
    input_string = b""""""
15
 
16
    reader = SourceFileTraceabilityReader_C()
17
 
18
    info = reader.read(input_string, file_path="NOT_RELEVANT")
19
 
20
    assert isinstance(info, SourceFileTraceabilityInfo)
21
    assert len(info.markers) == 0
22
 
23
 
24
def test_01_single_string():
25
    input_string = b"""\
26
// Unimportant comment.
27
"""
28
 
29
    reader = SourceFileTraceabilityReader_C()
30
 
31
    info = reader.read(input_string, file_path="NOT_RELEVANT")
32
 
33
    assert isinstance(info, SourceFileTraceabilityInfo)
34
    assert len(info.functions) == 0
35
    assert len(info.markers) == 0
36
 
37
 
38
def test_02_functions():
39
    input_string = b"""\
40
#include <stdio.h>
41
 
42
/**
43
 * Some text.
44
 *
45
 * @relation(REQ-1, scope=function)
46
 */
47
void hello_world(void) {
48
    print("hello world\\n");
49
}
50
 
51
/**
52
 * Some text.
53
 *
54
 * @relation(REQ-2, scope=function)
55
 */
56
void hello_world_2(void) {
57
    print("hello world\\n");
58
}
59
"""
60
 
61
    reader = SourceFileTraceabilityReader_C()
62
 
63
    info: SourceFileTraceabilityInfo = reader.read(
64
        input_string, file_path="foo.cpp"
65
    )
66
 
67
    assert isinstance(info, SourceFileTraceabilityInfo)
68
    assert len(info.markers) == 2
69
    assert info.markers[0].ng_source_line_begin == 6
70
    assert info.markers[0].ng_range_line_begin == 3
71
    assert info.markers[0].ng_range_line_end == 10
72
    assert info.markers[0].reqs_objs[0].ng_source_line == 6
73
    assert info.markers[0].reqs_objs[0].ng_source_column == 14
74
 
75
    assert info.markers[1].ng_source_line_begin == 15
76
    assert info.markers[1].ng_range_line_begin == 12
77
    assert info.markers[1].ng_range_line_end == 19
78
    assert info.markers[1].reqs_objs[0].ng_source_line == 15
79
    assert info.markers[1].reqs_objs[0].ng_source_column == 14
80
 
81
 
82
def test_03_functions_multiline():
83
    input_string = b"""\
84
#include <stdio.h>
85
 
86
/**
87
 * Some text.
88
 *
89
 * @relation(
90
 *   REQ-1, scope=function
91
 * )
92
 */
93
void hello_world(void) {
94
    print("hello world\\n");
95
}
96
 
97
/**
98
 * Some text.
99
 *
100
 * @relation(REQ-2,
101
 * scope=function)
102
 */
103
void hello_world_2(void) {
104
    print("hello world\\n");
105
}
106
"""
107
 
108
    reader = SourceFileTraceabilityReader_C()
109
 
110
    info: SourceFileTraceabilityInfo = reader.read(
111
        input_string, file_path="foo.cpp"
112
    )
113
 
114
    assert isinstance(info, SourceFileTraceabilityInfo)
115
    assert len(info.markers) == 2
116
    assert info.markers[0].ng_source_line_begin == 6
117
    assert info.markers[0].ng_range_line_begin == 3
118
    assert info.markers[0].ng_range_line_end == 12
119
    assert info.markers[0].reqs_objs[0].ng_source_line == 7
120
    assert info.markers[0].reqs_objs[0].ng_source_column == 6
121
 
122
    assert info.markers[1].ng_source_line_begin == 17
123
    assert info.markers[1].ng_range_line_begin == 14
124
    assert info.markers[1].ng_range_line_end == 22
125
    assert info.markers[1].reqs_objs[0].ng_source_line == 17
126
    assert info.markers[1].reqs_objs[0].ng_source_column == 14
127
 
128
 
129
def test_04_multiline_markers_with_underscores():
130
    """
131
    Bug: requirements UID not detected in source code when there's an EOL #2130
132
    https://github.com/strictdoc-project/strictdoc/issues/2130
133
    """
134
 
135
    input_string = b"""\
136
#include <stdio.h>
137
 
138
/**
139
 * @brief some text
140
 * @return some text.
141
 * @param[in] void
142
 * @param[out] void
143
 * @param[in, out] void
144
 * @pre
145
 * @post
146
 * @relation{INT_STP_016_0000, INT_STP_016_0001, INT_STP_016_0002,
147
 * scope=function}
148
 * @note Reference:
149
 */
150
stilib_result_t stilib_smu_start_state_check(void);
151
"""
152
 
153
    reader = SourceFileTraceabilityReader_C()
154
 
155
    info: SourceFileTraceabilityInfo = reader.read(
156
        input_string, file_path="foo.c"
157
    )
158
 
159
    assert isinstance(info, SourceFileTraceabilityInfo)
160
    assert len(info.markers) == 1
161
    assert info.markers[0].reqs == [
162
        "INT_STP_016_0000",
163
        "INT_STP_016_0001",
164
        "INT_STP_016_0002",
165
    ]
166
 
167
    assert info.markers[0].ng_source_line_begin == 11
168
    assert info.markers[0].ng_range_line_begin == 3
169
    assert info.markers[0].ng_range_line_end == 15
170
    assert info.markers[0].reqs_objs[0].ng_source_line == 11
171
    assert info.markers[0].reqs_objs[0].ng_source_column == 14
172
    assert info.markers[0].reqs_objs[1].ng_source_line == 11
173
    assert info.markers[0].reqs_objs[1].ng_source_column == 32
174
    assert info.markers[0].reqs_objs[2].ng_source_line == 11
175
    assert info.markers[0].reqs_objs[2].ng_source_column == 50
176
 
177
 
178
def test_20_node_fields():
179
    input_string = b"""\
180
#include <stdio.h>
181
 
182
/**
183
 * Some text.
184
 *
185
 * INTENTION: This
186
 *            is
187
 *            the
188
 *            intention.
189
 *
190
 * @relation(REQ-1, scope=function)
191
 */
192
void hello_world(void) {
193
    print("hello world\\n");
194
}
195
"""
196
 
197
    reader = SourceFileTraceabilityReader_C()
198
 
199
    info: SourceFileTraceabilityInfo = reader.read(
200
        input_string, file_path="foo.c"
201
    )
202
 
203
    assert isinstance(info, SourceFileTraceabilityInfo)
204
    assert len(info.markers) == 1
205
    assert info.markers[0].ng_source_line_begin == 11
206
    assert info.markers[0].ng_range_line_begin == 3
207
    assert info.markers[0].ng_range_line_end == 15
208
    assert info.markers[0].reqs_objs[0].ng_source_line == 11
209
    assert info.markers[0].reqs_objs[0].ng_source_column == 14
210
 
211
 
212
def test_90_edge_case_capitalized_field_with_colon_and_colon():
213
    """
214
    Ensure that there is no missing grammar token for a case reported by a user.
215
 
216
    Previously, StrictDoc would raise an exception related to Lark not finding
217
    a grammar token when parsing "L:LABEL: ..." kind of string (see below).
218
    This test makes sure that the parser works with no issues.
219
 
220
    https://github.com/strictdoc-project/strictdoc/issues/2342
221
    """
222
 
223
    input_string = b"""\
224
EFI_DEVICE_PATH *FileDevicePathFromConfig(EFI_HANDLE device,
225
					  CHAR16 *payloadpath)
226
{
227
    UINTN prefixlen = 0;
228
	   EFI_DEVICE_PATH *devpath = NULL;
229
 
230
	   LABELMODE lm = NOLABEL;
231
	   /* Check if payload path contains a
232
     * L:LABEL: item to specify a FAT partition or a
233
	    * C:LABEL: to specify a custom labeled FAT partition */
234
    if (StrnCmp(payloadpath, L"L:", 2) == 0) {
235
        lm = DOSFSLABEL;
236
    } else if (StrnCmp(payloadpath, L"C:", 2) == 0) {
237
		      lm = CUSTOMLABEL;
238
	   }
239
	   // ... truncated ...
240
}
241
"""
242
 
243
    reader = SourceFileTraceabilityReader_C()
244
 
245
    info: SourceFileTraceabilityInfo = reader.read(
246
        input_string, file_path="foo.c"
247
    )
248
 
249
    assert isinstance(info, SourceFileTraceabilityInfo)
250
    assert len(info.markers) == 0
251
 
252
 
253
def test_91_edge_case_capitalized_field_with_relation_marker():
254
    """
255
    Ensures that "@relation:" is not treated as an incomplete StrictDoc marker.
256
 
257
    Reduced fragment from this source file:
258
    https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/cpufreq/cpufreq-nforce2.c#n241
259
 
260
    https://github.com/strictdoc-project/strictdoc/issues/2342
261
    """
262
 
263
    input_string = b"""\
264
/**
265
 * nforce2_target - set a new CPUFreq policy
266
 * @policy: new policy
267
 * @target_freq: the target frequency
268
 * @relation: how that frequency relates to achieved frequency
269
 *  (CPUFREQ_RELATION_L or CPUFREQ_RELATION_H)
270
 *
271
 * Sets a new CPUFreq policy.
272
 */
273
static int nforce2_target(struct cpufreq_policy *policy,
274
			  unsigned int target_freq, unsigned int relation)
275
{
276
}
277
"""
278
 
279
    reader = SourceFileTraceabilityReader_C()
280
 
281
    info: SourceFileTraceabilityInfo = reader.read(
282
        input_string, file_path="foo.c"
283
    )
284
 
285
    assert isinstance(info, SourceFileTraceabilityInfo)
286
    assert len(info.markers) == 0
287
 
288
 
289
def test_92_edge_case_capitalized_letters():
290
    """
291
    Ensure that fragments such as "IP_VS_SCTP_S_COOKIE_REPLIED: C:COOKIE-ECHO"
292
    do not trigger parsing errors.
293
 
294
    Reduced fragment from this source file:
295
    https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/net/netfilter/ipvs/ip_vs_proto_sctp.c#n247
296
 
297
    https://github.com/strictdoc-project/strictdoc/issues/2342
298
    """
299
 
300
    input_string = b"""\
301
/*
302
 * IP_VS_SCTP_S_COOKIE_REPLIED: C:COOKIE-ECHO sent, wait for S:COOKIE-ACK
303
 */
304
void foobar(void) {}
305
"""
306
 
307
    reader = SourceFileTraceabilityReader_C()
308
 
309
    info: SourceFileTraceabilityInfo = reader.read(
310
        input_string, file_path="foo.c"
311
    )
312
 
313
    assert isinstance(info, SourceFileTraceabilityInfo)
314
    assert len(info.markers) == 0
315
 
316
 
317
def test_93_edge_case_capitalized_letters():
318
    """
319
    Ensure that fragments like "A: VMRUN:" do not trigger parsing errors.
320
 
321
    Reduced fragment from this source file:
322
    https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kvm/svm/sev.c#n4532
323
 
324
    https://github.com/strictdoc-project/strictdoc/issues/2342
325
    """
326
 
327
    input_string = b"""\
328
void sev_es_prepare_switch_to_guest(struct vcpu_svm *svm, struct sev_es_save_area *hostsa)
329
{
330
    struct kvm *kvm = svm->vcpu.kvm;
331
 
332
    /*
333
     * All host state for SEV-ES guests is categorized into three swap types
334
     * based on how it is handled by hardware during a world switch:
335
     *
336
     * A: VMRUN:   Host state saved in host save area
337
     */
338
}
339
"""
340
 
341
    reader = SourceFileTraceabilityReader_C()
342
 
343
    info: SourceFileTraceabilityInfo = reader.read(
344
        input_string, file_path="foo.c"
345
    )
346
 
347
    assert isinstance(info, SourceFileTraceabilityInfo)
348
    assert len(info.markers) == 0
349
 
350
 
351
def test_94_edge_case_field_name_then_newline_then_field_value():
352
    r"""
353
    Ensure that fragments with <field name>\n<field value...> do not trigger
354
    parsing errors.
355
 
356
    Reduced fragment from this source file:
357
    https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/video/fbdev/i810/i810_gtf.c#n115
358
 
359
    https://github.com/strictdoc-project/strictdoc/issues/2342
360
    """
361
 
362
    input_string = b"""\
363
/**
364
 * i810fb_encode_registers - encode @var to hardware register values
365
 * @var: pointer to var structure
366
 * @par: pointer to hardware par structure
367
 *
368
 * DESCRIPTION:
369
 * Timing values in @var will be converted to appropriate
370
 * register values of @par.
371
 */
372
void foobar(void) {}
373
"""
374
 
375
    reader = SourceFileTraceabilityReader_C()
376
 
377
    info: SourceFileTraceabilityInfo = reader.read(
378
        input_string, file_path="foo.c"
379
    )
380
 
381
    assert isinstance(info, SourceFileTraceabilityInfo)
382
    assert len(info.markers) == 0
383
    assert len(info.source_nodes) == 1
384
 
385
 
386
def test_95_namespace_with_function():
387
    r"""
388
    Ensure that namespace is parsed correctly for free functions.
389
    """
390
    input_string = b"""\
391
namespace math {
392
 
393
int add(int a, int b) {
394
    return a + b;
395
}
396
}
397
"""
398
    reader = SourceFileTraceabilityReader_C()
399
 
400
    info: SourceFileTraceabilityInfo = reader.read(
401
        input_string, file_path="foo.cpp"
402
    )
403
 
404
    assert isinstance(info, SourceFileTraceabilityInfo)
405
    assert len(info.markers) == 0
406
    assert len(info.functions) == 1
407
    assert len(info.source_nodes) == 0
408
 
409
    assert info.functions[0].name == "math::add(int a, int b)"
410
 
411
 
412
def test_96_namespace_with_class_function():
413
    r"""
414
    Ensure that namespace is parsed correctly for class member functions.
415
    """
416
    input_string = b"""\
417
namespace math {
418
 
419
class Adder {
420
public:
421
int add(int a, int b) {
422
    return a + b;
423
}
424
};
425
}
426
"""
427
    reader = SourceFileTraceabilityReader_C()
428
 
429
    info: SourceFileTraceabilityInfo = reader.read(
430
        input_string, file_path="foo.cpp"
431
    )
432
 
433
    assert isinstance(info, SourceFileTraceabilityInfo)
434
    assert len(info.markers) == 0
435
    assert len(info.functions) == 1
436
    assert len(info.source_nodes) == 0
437
 
438
    assert info.functions[0].name == "math::Adder::add(int a, int b)"
439
 
440
 
441
def test_97_multiline_function():
442
    r"""
443
    Ensure functions defined on multiple lines due to linting
444
    tools are parsed correctly. The saved name should not have any
445
    extra spaces, or newline characters.
446
 
447
    """
448
    input_string = b"""\
449
int add(int a,
450
    int b) {
451
    return a + b;
452
}
453
"""
454
    reader = SourceFileTraceabilityReader_C()
455
 
456
    info: SourceFileTraceabilityInfo = reader.read(
457
        input_string, file_path="foo.c"
458
    )
459
 
460
    assert isinstance(info, SourceFileTraceabilityInfo)
461
    assert len(info.markers) == 0
462
    assert len(info.functions) == 1
463
    assert len(info.source_nodes) == 0
464
 
465
    assert info.functions[0].name == "add(int a, int b)"
466
 
467
 
468
def test_100_known_c_macro_zephyr_test():
469
    """
470
    Ensure that Zephyr's function-like test macro is recognized as a
471
    function with its full macro invocation as the display name.
472
    """
473
 
474
    input_string = b"""\
475
ZTEST_USER(semaphore, test_k_sem_correct_count_limit)
476
{
477
  // Some code here...
478
}
479
"""
480
    reader = SourceFileTraceabilityReader_C()
481
 
482
    info: SourceFileTraceabilityInfo = reader.read(
483
        input_string, file_path="foo.cpp"
484
    )
485
 
486
    assert isinstance(info, SourceFileTraceabilityInfo)
487
    assert len(info.markers) == 0
488
    assert len(info.functions) == 1
489
    assert len(info.source_nodes) == 0
490
 
491
    assert (
492
        info.functions[0].name
493
        == "ZTEST_USER(semaphore, test_k_sem_correct_count_limit)"
494
    )
495
    assert (
496
        info.functions[0].display_name
497
        == "ZTEST_USER(semaphore, test_k_sem_correct_count_limit)"
498
    )
499
 
500
 
501
def test_101_known_c_macro_zephyr_test_with_comment():
502
    """
503
    Ensure that Zephyr's function-like test macro is recognized as a
504
    function with its full macro invocation as the display name.
505
    """
506
 
507
    input_string = b"""\
508
/*
509
 * Copyright (c) 2016, 2020 Intel Corporation
510
 *
511
 * SPDX-License-Identifier: Apache-2.0
512
 */
513
 
514
 
515
/**
516
 * @brief Test the max value a semaphore can be given and taken
517
 * @details
518
 * - Reset an initialized semaphore's count to zero.
519
 * - Give the semaphore by a thread and verify the semaphore's count is
520
 *   as expected.
521
 * - Verify the max count a semaphore can reach.
522
 * - Take the semaphore by a thread and verify the semaphore's count is
523
 *   as expected.
524
 * - Verify the max times a semaphore can be taken.
525
 * @ingroup kernel_semaphore_tests
526
 * @see k_sem_count_get(), k_sem_give()
527
 */
528
ZTEST_USER(semaphore, test_k_sem_correct_count_limit)
529
{
530
 
531
	/* reset an initialized semaphore's count to zero */
532
	k_sem_reset(&simple_sem);
533
	expect_k_sem_count_get(&simple_sem, 0U, "k_sem_reset failed: %u != %u");
534
 
535
	/* Give the semaphore by a thread and verify the semaphore's
536
	 * count is as expected
537
	 */
538
	for (int i = 1; i <= SEM_MAX_VAL; i++) {
539
		k_sem_give(&simple_sem);
540
		expect_k_sem_count_get_nomsg(&simple_sem, i);
541
	}
542
 
543
	/* Verify the max count a semaphore can reach
544
	 * continue to run k_sem_give,
545
	 * the count of simple_sem will not increase anymore
546
	 */
547
	for (int i = 0; i < 5; i++) {
548
		k_sem_give(&simple_sem);
549
		expect_k_sem_count_get_nomsg(&simple_sem, SEM_MAX_VAL);
550
	}
551
 
552
	/* Take the semaphore by a thread and verify the semaphore's
553
	 * count is as expected
554
	 */
555
	for (int i = SEM_MAX_VAL - 1; i >= 0; i--) {
556
		expect_k_sem_take_nomsg(&simple_sem, K_NO_WAIT, 0);
557
		expect_k_sem_count_get_nomsg(&simple_sem, i);
558
	}
559
 
560
	/* Verify the max times a semaphore can be taken
561
	 * continue to run k_sem_take, simple_sem can not be taken and
562
	 * it's count will be zero
563
	 */
564
	for (int i = 0; i < 5; i++) {
565
		expect_k_sem_take_nomsg(&simple_sem, K_NO_WAIT, -EBUSY);
566
 
567
		expect_k_sem_count_get_nomsg(&simple_sem, 0U);
568
	}
569
}
570
"""
571
    reader = SourceFileTraceabilityReader_C()
572
 
573
    info: SourceFileTraceabilityInfo = reader.read(
574
        input_string, file_path="foo.cpp"
575
    )
576
 
577
    assert isinstance(info, SourceFileTraceabilityInfo)
578
    assert len(info.markers) == 0
579
    assert len(info.functions) == 1
580
    assert len(info.source_nodes) == 1
581
 
582
    assert (
583
        info.functions[0].name
584
        == "ZTEST_USER(semaphore, test_k_sem_correct_count_limit)"
585
    )
586
    assert (
587
        info.functions[0].display_name
588
        == "ZTEST_USER(semaphore, test_k_sem_correct_count_limit)"
589
    )
590
 
591
 
592
def test_102_known_c_macro_linux_syscall_define():
593
    input_string = b"""\
594
SYSCALL_DEFINE2(clock_gettime, const clockid_t, which_clock,
595
                struct __kernel_timespec __user *, tp)
596
{
597
    // ...
598
    return error;
599
}
600
"""
601
    reader = SourceFileTraceabilityReader_C()
602
 
603
    info: SourceFileTraceabilityInfo = reader.read(
604
        input_string, file_path="foo.cpp"
605
    )
606
 
607
    assert isinstance(info, SourceFileTraceabilityInfo)
608
    assert len(info.markers) == 0
609
    assert len(info.functions) == 1
610
    assert len(info.source_nodes) == 0
611
 
612
    expected_function_name = (
613
        "SYSCALL_DEFINE2(clock_gettime, "
614
        "const clockid_t, which_clock, struct __kernel_timespec __user *, tp"
615
        ")"
616
    )
617
 
618
    assert info.functions[0].name == expected_function_name
619
    assert info.functions[0].display_name == expected_function_name
620
 
621
 
622
def test_103_error_recovery_linux_define_per_cpu_macro():
623
    """
624
    Ensure this snippet derived from Linux kernel/softirq.c doesn't drive the
625
    C reader into NotImplementedError. DEFINE_PER_CPU expectedly confuses the
626
    parser. The rest of the snippet is special context that triggered undefined
627
    behavior manifesting as NotImplemented error in StrictDoc <= 0.22.0a1.
628
    """
629
    input_string = b"""\
630
static DEFINE_PER_CPU(struct softirq_ctrl, softirq_ctrl) = {
631
\t.lock\t= INIT_LOCAL_LOCK(softirq_ctrl.lock),
632
};
633
 
634
struct lockdep_map bh_lock_map = {
635
\t.name\t\t\t= "local_bh",
636
};
637
 
638
static void handle_softirqs()
639
{
640
\tfoo("\\n", bar());
641
\tif (pending) { }
642
}
643
"""
644
 
645
    reader = SourceFileTraceabilityReader_C()
646
 
647
    info: SourceFileTraceabilityInfo = reader.read(
648
        input_string, file_path="foo.c"
649
    )
650
 
651
    assert isinstance(info, SourceFileTraceabilityInfo)
652
    assert len(info.markers) == 0
653
    assert len(info.functions) == 1
654
    assert len(info.source_nodes) == 0
655
 
656
    assert info.functions[0].name == "handle_softirqs()"
657
    assert info.functions[0].display_name == "handle_softirqs"