//! Las divergencias que el banco diferencial destapó contra `busybox awk` y `gawk --posix`. //! //! Cada una estuvo ROTA y daba un resultado plausible: ninguna reventaba, ninguna imprimía un //! error. Por eso están acá y no sólo en el banco — el banco compara contra oráculos que hay que //! tener en el store, y esto corre con `cargo test` en cualquier sitio. use std::process::{Command, Stdio}; use std::io::Write; fn awk(prog: &str, entrada: &str) -> String { let exe = env!("CARGO_BIN_EXE_akllay"); let mut h = Command::new(exe).arg(prog) .stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()) .spawn().expect("no pude lanzar akllay"); h.stdin.as_mut().unwrap().write_all(entrada.as_bytes()).unwrap(); let s = h.wait_with_output().unwrap(); String::from_utf8_lossy(&s.stdout).trim_end_matches('\n').to_string() } /// Leer NF tiene que forzar el partido en campos. Daba 0 hasta que alguien tocara un campo, porque /// el partido es perezoso — y eso es lo que hace barato asignar a `$0`. #[test] fn nf_se_lee_sin_tocar_un_campo() { assert_eq!(awk("{print NF}", " a b \n"), "2"); assert_eq!(awk("{print NF, $NF}", "a b c\n"), "3 c"); } /// `length` sin paréntesis es el único builtin al que POSIX se lo permite, y se leía como una /// variable llamada `length`, que siempre vale vacío. #[test] fn length_sin_parentesis() { assert_eq!(awk("{print length}", "abcd\n"), "4"); assert_eq!(awk("BEGIN{print length(\"hola\")}", ""), "4"); } /// Un array se pasa POR REFERENCIA. Se decide mirando si la función usa el parámetro como array, y /// `a["k"] = 1` tiene el nombre en el LUGAR de la asignación, no en el valor. #[test] fn los_arrays_se_pasan_por_referencia() { assert_eq!(awk(r#"function g(a){a["k"]="puesto"} BEGIN{g(A); print A["k"]}"#, ""), "puesto"); // Y los escalares siguen yendo por valor. assert_eq!(awk("function f(x){x=99} BEGIN{y=1; f(y); print y}", ""), "1"); } /// `substr` con inicio ≤ 0: se recorta a 1 y **el largo no se ajusta**. La lectura estricta de /// POSIX daría `a`; ni busybox ni gawk hacen eso, y lo que hay que ejecutar son los guiones /// escritos contra ellos. #[test] fn substr_con_indices_fuera_de_rango() { assert_eq!(awk(r#"BEGIN{print substr("abcdef", 0, 3)}"#, ""), "abc"); assert_eq!(awk(r#"BEGIN{print substr("abcdef", -1, 3)}"#, ""), "abc"); assert_eq!(awk(r#"BEGIN{print "["substr("abc", 10)"]"}"#, ""), "[]"); // Los no enteros se truncan hacia cero, no se redondean. assert_eq!(awk(r#"BEGIN{print substr("abcdef", 1.5, 2.5)}"#, ""), "ab"); } /// Una coincidencia VACÍA pegada al final de la anterior no sustituye. Es la única diferencia /// medida entre gawk y el awk de busybox en todo el banco, y gawk es quien sigue a POSIX. #[test] fn gsub_no_sustituye_el_vacio_pegado_a_la_coincidencia_anterior() { assert_eq!(awk(r#"BEGIN{s="xax"; n=gsub(/a*/,"-",s); print n, s}"#, ""), "3 -x-x-"); assert_eq!(awk(r#"BEGIN{s="abc"; n=gsub(/x*/,"-",s); print n, s}"#, ""), "4 -a-b-c-"); } /// El campo que viene de la entrada se compara como número si lo parece, y como cadena si no. #[test] fn strnum() { assert_eq!(awk("{print ($1 > $2)}", "10 9\n"), "1"); assert_eq!(awk(r#"BEGIN{print ("10" > "9")}"#, ""), "0"); assert_eq!(awk("{print $1}", "0012\n"), "0012"); assert_eq!(awk("{print $1+0}", "0012\n"), "12"); } /// `^` asocia a la DERECHA. El awk de busybox lo hace al revés y da 64. #[test] fn la_potencia_asocia_a_la_derecha() { assert_eq!(awk("BEGIN{print 2^3^2}", ""), "512"); } /// La ERE es leftmost-LONGEST, no leftmost-first: para `a|ab` sobre `"ab"` gana `ab`. #[test] fn la_regex_es_leftmost_longest() { assert_eq!(awk(r#"BEGIN{print match("ab", /a|ab/), RLENGTH}"#, ""), "1 2"); assert_eq!(awk(r#"BEGIN{s="ab"; sub(/a|ab/, "X", s); print s}"#, ""), "X"); } /// Un `exit` en el cuerpo ejecuta los END; uno dentro de END, no. #[test] fn exit_ejecuta_los_end() { assert_eq!(awk(r#"BEGIN{exit 0} END{print "end corre"}"#, ""), "end corre"); } /// El `%c` de printf mira el TIPO del argumento. #[test] fn printf_c_mira_el_tipo() { assert_eq!(awk(r#"BEGIN{printf "%c|%c\n", 65, "65"}"#, ""), "A|6"); } /// Los campos se rehacen con OFS al tocar cualquiera, y `$0` se reparte al asignarlo. #[test] fn campos_y_registro_son_dos_caras_del_mismo_dato() { assert_eq!(awk(r#"BEGIN{OFS="-"} {$1=$1; print}"#, "a b c\n"), "a-b-c"); assert_eq!(awk(r#"{$5="X"; print; print NF}"#, "a b\n"), "a b X\n5"); assert_eq!(awk(r#"{$0="p q r"; print $2, NF}"#, "a b\n"), "q 3"); assert_eq!(awk(r#"{NF=2; print}"#, "a b c d\n"), "a b"); }