fossil_delta/
lib.rs

1/*! Implementation of the delta algorithm used by fossil version control system.
2
3```rust
4use fossil_delta::{delta, deltainv};
5let a = b"line 1
6  yet another (a bit longer) line 2
7  yet another (a bit longer) line 3
8  yet another (a bit longer) line 4
9  yet another (a bit longer) line 5
10  yet another (a bit longer) line 6
11  yet another (a bit longer) line 7
12  yet another (a bit longer) line 8
13  yet another (a bit longer) line 9
14  yet another (a bit longer) line 10";
15let b = b"line 1
16  yet another (a bit longer) line 2
17  yet another (a bit longer) line 3
18  yet another (a bit longer) line 4
19  yet another (a bit longer) line 5
20  yet another (a bit longer) line 6
21  yet another (a bit longer) line 6 1/2
22  yet another (a bit longer) line 7
23  yet another (a bit longer) line 8
24  yet another (a bit longer) line 9
25  and finally last line 10";
26let d = delta(a, b);
27let s = deltainv(b, &d);
28assert_eq!(&s, a);
29assert_eq!(d.len(), 43);
30```
31*/
32
33use std::convert::TryInto;
34
35#[derive(Debug)]
36pub enum Error {
37    BadChecksum,
38    CopyExceedsSize,
39    CopyExtendsPasteEndOfInput,
40    UnexpectedCharacter(u8),
41}
42
43const NHASH: usize = 16;
44/// converts integer to String in base 64
45pub fn b64str(n: u32) -> String {
46    if n == 0 {
47        String::from("0")
48    } else {
49        let mut res = String::new();
50        let mut _n = n;
51        while _n > 0 {
52            res.insert(0, B64DIGITS[(_n & 63) as usize]);
53            _n = _n >> 6;
54        }
55        res
56    }
57}
58
59/// converts base 64 str to u32
60pub fn b64int<T: AsRef<[u8]> + ?Sized>(a: &T) -> u32 {
61    b64int_read(a.as_ref()).0 as u32
62}
63
64pub fn b64int_read<T: AsRef<[u8]> + ?Sized>(a: &T) -> (usize, &[u8]) {
65    let mut res = 0_usize;
66    for (j, i) in a.as_ref().iter().enumerate() {
67        let k = B64VALUES[(i & 127) as usize];
68        if k == 255 {
69             let a = a.as_ref();
70            return (res, &a[j..]);
71        }
72        res = (res << 6) + (k as usize);
73    }
74    (res, b"")
75}
76
77const B64DIGITS: [char; 64] = [
78    '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I',
79    'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '_', 'a',
80    'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
81    'u', 'v', 'w', 'x', 'y', 'z', '~',
82];
83const B64VALUES: [u8; 128] = [
84    255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
85    255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
86    255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
87    255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 0u8, 1u8, 2u8, 3u8, 4u8, 5u8,
88    6u8, 7u8, 8u8, 9u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 10u8, 11u8, 12u8, 13u8,
89    14u8, 15u8, 16u8, 17u8, 18u8, 19u8, 20u8, 21u8, 22u8, 23u8, 24u8, 25u8, 26u8, 27u8, 28u8, 29u8,
90    30u8, 31u8, 32u8, 33u8, 34u8, 35u8, 255u8, 255u8, 255u8, 255u8, 36u8, 255u8, 37u8, 38u8, 39u8,
91    40u8, 41u8, 42u8, 43u8, 44u8, 45u8, 46u8, 47u8, 48u8, 49u8, 50u8, 51u8, 52u8, 53u8, 54u8, 55u8,
92    56u8, 57u8, 58u8, 59u8, 60u8, 61u8, 62u8, 255u8, 255u8, 255u8, 63u8, 255u8,
93];
94
95/// Return the number digits in the base-64 representation of a positive integer
96pub fn digit_count(v: usize) -> usize {
97    let mut x = 64;
98    for i in 1..10 {
99        if x > v {
100            return i;
101        };
102        x = x << 6;
103    }
104    11
105}
106
107/// Compute a 32-bit big-endian checksum on the N-byte buffer.  If the
108/// buffer is not a multiple of 4 bytes length, compute the sum that would
109/// have occurred if the buffer was padded with zeros to the next multiple
110/// of four bytes.
111fn checksum<T: AsRef<[u8]>>(z_in: T) -> u32 {
112    let it = z_in.as_ref().chunks_exact(4);
113    let b = it.remainder();
114    let a_b: [u8; 4] = match b.len() {
115        0 => [0, 0, 0, 0],
116        1 => [b[0], 0, 0, 0],
117        2 => [b[0], b[1], 0, 0],
118        _ => [b[0], b[1], b[2], 0],
119    };
120    let mut s: u32 = u32::from_be_bytes(a_b);
121    for b in it {
122        let a_b: &[u8; 4] = unsafe { &*(b.as_ptr() as *const [u8; 4]) };
123        let a = u32::from_be_bytes(*a_b);
124        s = s.overflowing_add(a).0;
125    }
126    s
127}
128/// Generate new delta in given mutable string reference
129///
130/// ## Output Format:
131///
132/// The delta begins with a base64 number followed by a newline.  This
133/// number is the number of bytes in the TARGET file.  Thus, given a
134/// delta file z, a program can compute the size of the output file
135/// simply by reading the first line and decoding the base-64 number
136/// found there. The delta_output_size() routine does exactly this.
137///
138/// After the initial size number, the delta consists of a series of
139/// literal text segments and commands to copy from the SOURCE file.
140/// A copy command looks like this:
141///
142/// <pre>NNN@MMM,</pre>
143///
144/// where `NNN` is the number of bytes to be copied and `MMM` is the offset
145/// into the source file of the first byte (both base-64).   If `NNN` is 0
146/// it means copy the rest of the input file.  Literal text is like this:
147///
148/// <pre>NNN:TTTTT</pre>
149///
150/// where `NNN` is the number of bytes of text (base-64) and `TTTTT` is
151/// the text.
152/// The last term is of the form
153///
154/// <pre>NNN;</pre>
155///
156/// In this case, `NNN` is a 32-bit bigendian checksum of the output file
157/// that can be used to verify that the delta applied correctly.  All
158/// numbers are in base-64.
159///
160/// Pure text files generate a pure text delta.  Binary files generate a
161/// delta that may contain some binary data.
162///
163/// ## Algorithm:
164///
165/// The encoder first builds a hash table to help it find matching
166/// patterns in the source file.  16-byte chunks of the source file
167/// sampled at evenly spaced intervals are used to populate the hash
168/// table.
169///
170/// Next we begin scanning the target file using a sliding 16-byte
171/// window.  The hash of the 16-byte window in the target is used to
172/// search for a matching section in the source file.  When a match
173/// is found, a copy command is added to the delta.  An effort is
174/// made to extend the matching section to regions that come before
175/// and after the 16-byte hash window.  A copy command is only issued
176/// if the result would use less space that just quoting the text
177/// literally. Literal text is added to the delta for sections that
178/// do not match or which can not be encoded efficiently using copy
179/// commands.
180///
181pub fn generate_delta<T: AsRef<[u8]>, V: AsRef<[u8]>>(
182    z_out_t: T,         /* The target text */
183    z_src_t: V,         /* The source text */
184    z_delta: &mut Vec<u8>, /* A string to hold the resulting delta */
185) {
186    z_delta.clear();
187    let z_src = z_src_t.as_ref();
188    let z_out = z_out_t.as_ref();
189    // match block backward
190    let mb_backward = |i, j, n| {
191        if i == 0 || j <= n {
192            return 0;
193        }
194        let mut k = i - 1;
195        let mut m = j - 1;
196        let n_1 = if n == 0 { 0 } else { n - 1 };
197        while k > 0 && m > n_1 {
198            if z_src[k] != z_out[m] {
199                return i - k - 1;
200            }
201            k -= 1;
202            m -= 1;
203        }
204        i - k - 1
205    };
206    // match block forward
207    let mb_forward = |i, j| {
208        let mut k = i + 1;
209        let mut m = j + 1;
210        while k < z_src.len() && m < z_out.len() {
211            if z_src[k] != z_out[m] {
212                return k - i - 1;
213            }
214            k += 1;
215            m += 1;
216        }
217        if z_src.len() - i < z_out.len() - j {
218            z_src.len() - i - 1
219        } else {
220            z_out.len() - j - 1
221        }
222    };
223    z_delta.extend_from_slice(b64str(z_out.len() as u32).as_bytes());
224    z_delta.push(b'\n');
225
226    /* If the source file is very small, it means that we have no
227     ** chance of ever doing a copy command.  Just output a single
228     ** literal segment for the entire target and exit.
229     */
230    if z_src.len() <= NHASH {
231        z_delta.extend_from_slice(b64str(z_out.len() as u32).as_bytes());
232        z_delta.push(b':');
233        z_delta.extend_from_slice(z_out);
234        z_delta.extend_from_slice(b64str(checksum(&z_out)).as_bytes());
235        z_delta.push(b';');
236        return;
237    }
238    /* Compute the hash table used to locate matching sections in the
239     ** source file.
240     */
241    let n_hash = z_src.len() / NHASH;
242    let mut collide = vec![0xffff_ffff_u32; 2 * n_hash];
243    let mut h = Hash::new();
244    for i in 0..n_hash {
245        h.init(&z_src[(NHASH * i)..]);
246        let hv = h.as_usize() % n_hash + n_hash;
247        collide[i] = collide[hv];
248        collide[hv] = i as u32;
249    }
250    let mut base = 0usize;
251    while base + NHASH < z_out.len() {
252        let mut i = 0;
253        h.init(&z_out[base..]);
254        let mut best_count = 0;
255        let mut best_offset = 0;
256        let mut best_lit_size = 0;
257        loop {
258            let hv = h.as_usize() % n_hash;
259            let mut i_block = collide[n_hash + hv];
260            let mut limit = 250;
261            while i_block != 0xffff_ffff && limit > 0 {
262                limit -= 1;
263                let i_src = (i_block as usize) * NHASH;
264                if z_src[i_src] == z_out[base + i] {
265                    let j = mb_forward(i_src, base + i);
266                    let k = mb_backward(i_src, base + i, base);
267                    let ofst = i_src - k;
268                    let cnt = j + k + 1;
269                    let litsz = i - k;
270                    let sz = digit_count(litsz) + digit_count(cnt) + digit_count(ofst) + 3;
271                    if cnt > sz && cnt > best_count {
272                        best_count = cnt;
273                        best_offset = ofst;
274                        best_lit_size = litsz;
275                    }
276                }
277                i_block = collide[i_block as usize];
278            }
279            if best_count > 0 {
280                if best_lit_size > 0 {
281                    z_delta.extend_from_slice(b64str(best_lit_size as u32).as_bytes());
282                    z_delta.push(b':');
283                    z_delta.extend_from_slice(&z_out[base..(base + best_lit_size)]);
284                    base += best_lit_size;
285                }
286                base += best_count;
287                z_delta.extend_from_slice(b64str(best_count as u32).as_bytes());
288                z_delta.push(b'@');
289                z_delta.extend_from_slice(b64str(best_offset as u32).as_bytes());
290                z_delta.push(b',');
291                break;
292            } else if base + i + NHASH >= z_out.len() {
293                z_delta.extend_from_slice(b64str((z_out.len() - base) as u32).as_bytes());
294                z_delta.push(b':');
295                z_delta.extend_from_slice(&z_out[base..]);
296                base = z_out.len();
297                break;
298            } else {
299                h.update(z_out[base + NHASH + i]);
300                i += 1;
301            }
302        }
303    }
304    if base < z_out.len() {
305        z_delta.extend_from_slice(b64str((z_out.len() - base) as u32).as_bytes());
306        z_delta.push(b':');
307        z_delta.extend_from_slice(&z_out[base..]);
308    }
309    z_delta.extend_from_slice(b64str(checksum(z_out)).as_bytes());
310    z_delta.push(b';');
311}
312
313/// Creates delta and returns it as a String
314/// see [`generate_delta`]
315pub fn delta<T: AsRef<[u8]>, V: AsRef<[u8]>>(a: T, b: V) -> Vec<u8> {
316    let mut d = Vec::with_capacity(b.as_ref().len() + 60);
317    generate_delta(a, b, &mut d);
318    d
319}
320
321pub fn apply<T: AsRef<[u8]>, V: AsRef<[u8]>>(source: T, delta: V) -> Result<Vec<u8>, Error> {
322    let source = source.as_ref();
323    let mut total = 0;
324    let source_length = source.len();
325    let (total_length, mut delta) = b64int_read(&delta);
326    let mut output = Vec::with_capacity(total_length);
327
328    delta = &delta[1..];
329    while delta.len() > 0 {
330        let (cnt, delta_read) = b64int_read(delta);
331        match delta_read[0] {
332            b'@' => {
333                total += cnt;
334                let (offset, delta_read) = b64int_read(&delta_read[1..]);
335                if total > total_length {
336                    return Err(Error::CopyExceedsSize);
337                }
338                if offset + cnt > source_length {
339                    return Err(Error::CopyExtendsPasteEndOfInput);
340                }
341                output.extend_from_slice(&source[offset..(offset + cnt)]);
342                delta = &delta_read[1..];
343            }
344            b':'=> {
345                total += cnt;
346                let i = delta.len() - delta_read.len() + 1;
347                if total > total_length {
348                    return Err(Error::CopyExceedsSize);
349                }
350                output.extend_from_slice(&delta[i..(cnt + i)]);
351                delta = &delta_read[(1 + cnt)..];
352            }
353            b';' => {
354                if cnt != checksum(&output).try_into().unwrap() {
355                    return Err(Error::BadChecksum);
356                }
357                return Ok(output);
358            }
359            c => {
360                return Err(Error::UnexpectedCharacter(c));
361            }
362        }
363    }
364    
365    Ok(output)
366}
367
368/// Return the size (in bytes) of the output from applying
369/// a delta.
370///
371/// This routine is provided so that an procedure that is able
372/// to call delta_apply() can learn how much space is required
373/// for the output and hence allocate nor more space that is really
374/// needed.
375///
376pub fn delta_output_size<T: AsRef<[u8]>>(z_delta: T) -> usize {
377    b64int(&z_delta) as usize
378}
379
380/// Given the current version of text value `b_txt` and delta value as `d_txt`
381/// this function returns the previous version of text b_txt.
382pub fn deltainv<T: AsRef<[u8]>, V: AsRef<[u8]>>(b_txt: T, d_txt: V) -> Vec<u8> {
383    let (total_length, mut d_src) = b64int_read(&d_txt);
384
385    let mut a_res = Vec::with_capacity(total_length);
386    let b_txt = b_txt.as_ref();
387    let b_bytes = b_txt;
388    let d_txt = d_txt.as_ref();
389    d_src = &d_src[1..];
390    while d_src.len() > 0 {
391        let (cnt, d1_src) = b64int_read(d_src);
392        match d1_src[0] {
393            b'@' => {
394                let (ofst, d1_src) = b64int_read(&d1_src[1..]);
395                a_res.extend_from_slice(&b_bytes[ofst..(ofst + cnt)]);
396                d_src = &d1_src[1..];
397            }
398            b':' => {
399                let i = d_txt.len() - d1_src.len() + 1;
400                a_res.extend_from_slice(&d_txt[i..(cnt + i)]);
401                d_src = &d1_src[(1 + cnt)..];
402            }
403            b';' => return a_res,
404            _ => {
405                let msg = format!(
406                    r#"Error in applying delta
407        txt: {:?}
408        -----------------------------
409        delta: {:?}
410        =============================
411        index: {}
412        "#,
413                    b_txt,
414                    d_txt,
415                    d_txt.len() - d1_src.len()
416                );
417                panic!("{}", msg)
418            }
419        }
420    }
421    a_res
422}
423
424const NHASH_1: usize = NHASH - 1;
425const NHASHI32: i32 = NHASH as i32;
426struct Hash {
427    a: u16,
428    b: u16,
429    i: usize,
430    z: [u8; NHASH],
431}
432impl Hash {
433    fn new() -> Self {
434        Hash {
435            a: 0,
436            b: 0,
437            i: 0,
438            z: [0; NHASH],
439        }
440    }
441    /// Initialize the rolling hash using the first NHASH characters of z[]
442    fn init(&mut self, z: &[u8]) {
443        let mut a = z[0] as u32;
444        let mut b = z[0] as u32;
445        self.z[0] = z[0];
446        for i in 1..NHASH {
447            a = (a + (z[i] as u32)) & 0xffff;
448            b = (b + a) & 0xffff;
449            self.z[i] = z[i];
450        }
451        self.a = a as u16;
452        self.b = b as u16;
453        self.i = 0;
454    }
455    /// Advance the rolling hash by a single character c
456    fn update(&mut self, c: u8) {
457        let old = self.z[self.i];
458        self.z[self.i] = c;
459        self.i = (self.i + 1) & NHASH_1;
460        let a = (self.a as i32) + (c as i32) - (old as i32);
461        let b = (self.b as i32) - NHASHI32 * (old as i32) + (a & 0xffff);
462        self.a = (a & 0xffff) as u16;
463        self.b = (b & 0xffff) as u16;
464    }
465    /// Return a usize hash value
466    fn as_usize(&self) -> usize {
467        (self.a as usize) | ((self.b as usize) << 16)
468    }
469}
470#[cfg(test)]
471mod tests {
472    use super::*;
473    #[test]
474    fn b64_works() {
475        for i in 0..1000 {
476            let s = b64str(i);
477            let s1 = b64str(i + 0x1_00_0000);
478            assert_eq!(i, b64int(&s));
479            assert_eq!(i, b64int(&s1) - 0x1_00_0000);
480        }
481    }
482    #[test]
483    fn test_hash_update() {
484        let mut h = Hash::new();
485        h.init(b"0123456789ABCDEFFEDCBA9876543210");
486        assert_eq!(h.as_usize(), 0x1cbb03a2);
487        let mut h2 = Hash::new();
488        h2.init(b"123456789ABCDEFFEDCBA9876543210");
489        h.update(b'F');
490        assert_eq!(h.as_usize(), h2.as_usize())
491    }
492    #[test]
493    fn delta_gen() {
494        let old = include_str!("test-data/file-a.txt");
495        let cur = include_str!("test-data/file-b.txt");
496        let d1: &[u8] = include_bytes!("test-data/file-delta.txt");
497        let mut d = Vec::new();
498        generate_delta(&cur, &old, &mut d);
499        assert_eq!(d.as_slice(), d1);
500    }
501    #[test]
502    fn round_trip_test() {
503        let a = b"line 1
504            yet another (a bit longer) line 2
505            yet another (a bit longer) line 3
506            yet another (a bit longer) line 4
507            yet another (a bit longer) line 5
508            yet another (a bit longer) line 6
509            yet another (a bit longer) line 7
510            yet another (a bit longer) line 8
511            yet another (a bit longer) line 9
512            yet another (a bit longer) line 10";
513        let b = b"line 1
514            yet another (a bit longer) line 2
515            yet another (a bit longer) line 3
516            yet another (a bit longer) line 4
517            yet another (a bit longer) line 5
518            yet another (a bit longer) line 6
519            yet another (a bit longer) line 6 1/2
520            yet another (a bit longer) line 7
521            yet another (a bit longer) line 8
522            yet another (a bit longer) line 9
523            and finally last line 10";
524        let d = delta(a, b);
525        println!("delta:{:?}", &d);
526        let s = deltainv(b, &d);
527        assert_eq!(&s, a);
528        assert_eq!(d.len(), 43);
529    }
530    #[test]
531    fn round_trip_test2() {
532        let a = r#"def do_Expression(self, node):\n    '''An inner expression'''\n    self.visit(node.body)\n"#.as_bytes();
533        let b = r#"sion(self, node):\n    '''An inner expression'''\n    self.visit(node.body)\n"#.as_bytes();
534        println!(
535            "a.len={}, b.len={}, b64={}",
536            a.len(),
537            b.len(),
538            &b64str(a.len() as u32)
539        );
540        let d = delta(b, a);
541        println!("delta:{:?}", &d);
542        let s = deltainv(a, &d);
543        assert_eq!(&s, b);
544    }
545    #[test]
546    fn empty_txt() {
547        let a = "".as_bytes();
548        let b = r#"line 1
549      yet another (a bit longer) line 2
550      yet another (a bit longer) line 3
551      yet another (a bit longer) line 4
552      yet another (a bit longer) line 5
553      yet another (a bit longer) line 6
554      yet another (a bit longer) line 6 1/2
555      yet another (a bit longer) line 7
556      yet another (a bit longer) line 8
557      yet another (a bit longer) line 9
558      and finally last line 10"#.as_bytes();
559        let d = delta(b, a);
560        println!("empty delta:{:?}", &d);
561        let s = deltainv(a, &d);
562        assert_eq!(b, &s);
563    }
564    #[test]
565    fn test_deltainv() {
566        let old = include_bytes!("test-data/file-a.txt");
567        let cur = include_bytes!("test-data/file-b.txt");
568        let d1: &[u8] = include_bytes!("test-data/file-delta.txt");
569        let res = deltainv(cur, d1);
570        assert_eq!(&res[..30], &old[..30]);
571    }
572    #[test] 
573    fn apply_test() {
574        let old = include_str!("test-data/file-a.txt");
575        let cur = include_str!("test-data/file-b.txt");
576        let d1: &[u8] = include_bytes!("test-data/file-delta.txt");
577        let out = apply(&old, &d1).unwrap();
578        assert_eq!(cur, String::from_utf8_lossy(&out));
579    }
580    #[test]
581    fn test_bug_001() {
582        let a=b"send-snap\nimport zmq\n#c.user_dict.pop('sendsnap', None)\n@others\nmsg = \"snapshot %s\"% snap()\nsend(msg)\n#msg = \"getat 2019-07-11 10:06:21\"\n#res = send(msg, True)\n#with open('/tmp/proba', 'w') as out:\n#    out.write(res)\ng.es('ok')\n";
583        let b=b"send-snap\nimport zmq\n#c.user_dict.pop('sendsnap', None)\n@others\nmsg = \"snapshot %s\"% snap()\n\nsend(msg)\n#msg = \"getat 2019-07-11 10:06:21\"\n#res = send(msg, True)\n#with open('/tmp/proba', 'w') as out:\n#    out.write(res)\ng.es('ok')\n";
584        let d = b"3a\n1S@0,29@1T,31_Pqh;";
585        let d1 = delta(&a, &b);
586        assert_eq!(&d1, &d);
587        let s = deltainv(b, &d1);
588        assert_eq!(&s, a);
589    }
590    #[test]
591    fn test_bug_002() {
592        let a="from student import moja_tajna_funkcija\n\ndef check(a, b):\n    assert moja_tajna_funkcija(a, b) == a + b, \"Функција не даје добар резултат за аргументе: %r и %r\"%(a, b)\n\nif __name__ == '__main__':\n    for x in range(-100, 101):\n        for y in range(-100, 101):\n            check(x, y)\n    print(\"Функција ради коректно\")\n";
593        let b="from student import moja_tajna_funkcija\n\ndef check(a, b):\n    assert moja_tajna_funkcija(a, b) == a + b, \"Функција не даје добар резултат за аргументе: %r и %r\"%(a, b)\n\nif __name__ == '__main__':\n    for x in range(-100, 101):\n        for y in range(-100, 101):\n            check(x, y)\n    print(\"Није пронађена грешка у твом програму.\")\n";
594        let d = delta(&b, &a);
595        let mut d1 = Vec::new();
596        d1.extend_from_slice(b"6P\n5H@0,18:\x9d\xd0\xb8\xd1\x98\xd0\xb5 \xd0\xbf\xd1\x80\xd0\xbe\xd0\xbd\xd0\xb0\xd1\x92\xd0\xb5\xd0\xbd\xd0\xb0 \xd0\xb3\xd1\x80\xd0\xb5\xd1\x88\xd0\xba\xd0\xb0 \xd1\x83 \xd1\x82\xd0\xb2\xd0\xbe\xd0\xbc \xd0\xbf\xd1\x80\xd0\xbe\xd0\xb3\xd1\x80\xd0\xb0\xd0\xbc\xd1\x83.\")\n2mdlCq;");
597        assert_eq!(d, d1);
598    }
599    #[allow(dead_code)]
600    static BUG002_A:&str = r#"from student import moja_tajna_funkcija
601
602def check(a, b):
603    assert moja_tajna_funkcija(a, b) == a + b, "Функција не даје добар резултат за аргументе: %r и %r"%(a, b)
604
605if __name__ == '__main__':
606    for x in range(-100, 101):
607        for y in range(-100, 101):
608            check(x, y)
609    print("Функција ради коректно")"#;
610
611    #[allow(dead_code)]
612    static BUG002_B:&str = r#"from student import moja_tajna_funkcija
613
614def check(a, b):
615    assert moja_tajna_funkcija(a, b) == a + b, "Функција не даје добар резултат за аргументе: %r и %r"%(a, b)
616
617if __name__ == '__main__':
618    for x in range(-100, 101):
619        for y in range(-100, 101):
620            check(x, y)
621    print("Није пронађена грешка у твом програму.")"#;
622}