summaryrefslogtreecommitdiffstats
path: root/tests/apply.test
blob: ba19b81ae4be82e57ceb552424080a6ff9238de3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# Commands covered:  apply
#
# This file contains a collection of tests for one or more of the Tcl
# built-in commands.  Sourcing this file into Tcl runs the tests and
# generates output for errors.  No output means no errors were found.
#
# Copyright (c) 1991-1993 The Regents of the University of California.
# Copyright (c) 1994-1996 Sun Microsystems, Inc.
# Copyright (c) 1998-1999 by Scriptics Corporation.
# Copyright (c) 2005-2006 Miguel Sofer
#
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.

if {[lsearch [namespace children] ::tcltest] == -1} {
    package require tcltest 2.2
    namespace import -force ::tcltest::*
}

if {[info commands ::apply] eq {}} {
    return
}

testConstraint memory [llength [info commands memory]]

# Tests for wrong number of arguments

test apply-1.1 {too few arguments} -returnCodes error -body {
    apply
} -result {wrong # args: should be "apply lambdaExpr ?arg ...?"}

# Tests for malformed lambda

test apply-2.0 {malformed lambda} -returnCodes error -body {
    set lambda a
    apply $lambda
} -result {can't interpret "a" as a lambda expression}
test apply-2.1 {malformed lambda} -returnCodes error -body {
    set lambda [list a b c d]
    apply $lambda
} -result {can't interpret "a b c d" as a lambda expression}
test apply-2.2 {malformed lambda} {
    set lambda [list {{}} boo]
    list [catch {apply $lambda} msg] $msg $::errorInfo
} {1 {argument with no name} {argument with no name
    (parsing lambda expression "{{}} boo")
    invoked from within
"apply $lambda"}}
test apply-2.3 {malformed lambda} {
    set lambda [list {{a b c}} boo]
    list [catch {apply $lambda} msg] $msg $::errorInfo
} {1 {too many fields in argument specifier "a b c"} {too many fields in argument specifier "a b c"
    (parsing lambda expression "{{a b c}} boo")
    invoked from within
"apply $lambda"}}
test apply-2.4 {malformed lambda} {
    set lambda [list a(1) boo]
    list [catch {apply $lambda} msg] $msg $::errorInfo
} {1 {formal parameter "a(1)" is an array element} {formal parameter "a(1)" is an array element
    (parsing lambda expression "a(1) boo")
    invoked from within
"apply $lambda"}}
test apply-2.5 {malformed lambda} {
    set lambda [list a::b boo]
    list [catch {apply $lambda} msg] $msg $::errorInfo
} {1 {formal parameter "a::b" is not a simple name} {formal parameter "a::b" is not a simple name
    (parsing lambda expression "a::b boo")
    invoked from within
"apply $lambda"}}

# Tests for runtime errors in the lambda expression

test apply-3.1 {non-existing namespace} -body {
    apply [list x {set x 1} ::NONEXIST::FOR::SURE] x
} -returnCodes error -result {namespace "::NONEXIST::FOR::SURE" not found}
test apply-3.2 {non-existing namespace} -body {
    namespace eval ::NONEXIST::FOR::SURE {}
    set lambda [list x {set x 1} ::NONEXIST::FOR::SURE]
    apply $lambda x
    namespace delete ::NONEXIST
    apply $lambda x
} -returnCodes error -result {namespace "::NONEXIST::FOR::SURE" not found}
test apply-3.3 {non-existing namespace} -body {
    apply [list x {set x 1} NONEXIST::FOR::SURE] x
} -returnCodes error -result {namespace "::NONEXIST::FOR::SURE" not found}
test apply-3.4 {non-existing namespace} -body {
    namespace eval ::NONEXIST::FOR::SURE {}
    set lambda [list x {set x 1} NONEXIST::FOR::SURE]
    apply $lambda x
    namespace delete ::NONEXIST
    apply $lambda x
} -returnCodes error -result {namespace "::NONEXIST::FOR::SURE" not found}

test apply-4.1 {error in arguments to lambda expression} -body {
    set lambda [list x {set x 1}]
    apply $lambda
} -returnCodes error -result {wrong # args: should be "apply lambdaExpr x"}
test apply-4.2 {error in arguments to lambda expression} -body {
    set lambda [list x {set x 1}]
    apply $lambda a b
} -returnCodes error -result {wrong # args: should be "apply lambdaExpr x"}
test apply-4.3 {error in arguments to lambda expression} -body {
    interp alias {} foo {} ::apply [list x {set x 1}]
    foo a b
} -cleanup {
    rename foo {}
} -returnCodes error -result {wrong # args: should be "foo x"}
test apply-4.4 {error in arguments to lambda expression} -body {
    interp alias {} foo {} ::apply [list x {set x 1}] a
    foo b
} -cleanup {
    rename foo {}
} -returnCodes error -result {wrong # args: should be "foo"}
test apply-4.5 {error in arguments to lambda expression} -body {
    set lambda [list x {set x 1}]
    namespace eval a {
	namespace ensemble create -command ::bar -map {id {::a::const foo}}
	proc const val { return $val }
	proc alias {object slot = command args} {
	    set map [namespace ensemble configure $object -map]
	    dict set map $slot [linsert $args 0 $command]
	    namespace ensemble configure $object -map $map
	}
	proc method {object name params body} {
	    set params [linsert $params 0 self]
	    alias $object $name = ::apply [list $params $body] $object
	}
	method ::bar boo x {return "[expr {$x*$x}] - $self"}
    }
    bar boo
} -cleanup {
    namespace delete ::a
} -returnCodes error -result {wrong # args: should be "bar boo x"}

test apply-5.1 {runtime error in lambda expression} {
    set lambda [list {} {error foo}]
    set res [catch {apply $lambda}]
    list $res $::errorInfo
} {1 {foo
    while executing
"error foo"
    (lambda term "{} {error foo}" line 1)
    invoked from within
"apply $lambda"}}

# Tests for correct execution; as the implementation is the same as that for
# procs, the general functionality is mostly tested elsewhere

test apply-6.1 {info level} {
    set lev [info level]
    set lambda [list {} {info level}]
    expr {[apply $lambda] - $lev}
} 1
test apply-6.2 {info level} {
    set lambda [list {} {info level 0}]
    apply $lambda
} {apply {{} {info level 0}}}
test apply-6.3 {info level} {
    set lambda [list args {info level 0}]
    apply $lambda x y
} {apply {args {info level 0}} x y}

# Tests for correct namespace scope

namespace eval ::testApply {
    proc testApply args {return testApply}
}

test apply-7.1 {namespace access} {
    set ::testApply::x 0
    set body {set x 1; set x}
    list [apply [list args $body ::testApply]] $::testApply::x
} {1 0}
test apply-7.2 {namespace access} {
    set ::testApply::x 0
    set body {variable x; set x}
    list [apply [list args $body ::testApply]] $::testApply::x
} {0 0}
test apply-7.3 {namespace access} {
    set ::testApply::x 0
    set body {variable x; set x 1}
    list [apply [list args $body ::testApply]] $::testApply::x
} {1 1}
test apply-7.4 {namespace access} {
    set ::testApply::x 0
    set body {testApply}
    apply [list args $body ::testApply]
} testApply
test apply-7.5 {namespace access} {
    set ::testApply::x 0
    set body {set x 1; set x}
    list [apply [list args $body testApply]] $::testApply::x
} {1 0}
test apply-7.6 {namespace access} {
    set ::testApply::x 0
    set body {variable x; set x}
    list [apply [list args $body testApply]] $::testApply::x
} {0 0}
test apply-7.7 {namespace access} {
    set ::testApply::x 0
    set body {variable x; set x 1}
    list [apply [list args $body testApply]] $::testApply::x
} {1 1}
test apply-7.8 {namespace access} {
    set ::testApply::x 0
    set body {testApply}
    apply [list args $body testApply]
} testApply

# Tests for correct argument treatment

set applyBody {
    set res {}
    foreach v [info locals] {
	if {$v eq "res"} continue
	lappend res [list $v [set $v]]
    }
    set res
}

test apply-8.1 {args treatment} {
    apply [list args $applyBody] 1 2 3
} {{args {1 2 3}}}
test apply-8.2 {args treatment} {
    apply [list {x args} $applyBody] 1 2
} {{x 1} {args 2}}
test apply-8.3 {args treatment} {
    apply [list {x args} $applyBody] 1 2 3
} {{x 1} {args {2 3}}}
test apply-8.4 {default values} {
    apply [list {{x 1} {y 2}} $applyBody] 
} {{x 1} {y 2}}
test apply-8.5 {default values} {
    apply [list {{x 1} {y 2}} $applyBody] 3 4
} {{x 3} {y 4}}
test apply-8.6 {default values} {
    apply [list {{x 1} {y 2}} $applyBody] 3
} {{x 3} {y 2}}
test apply-8.7 {default values} {
    apply [list {x {y 2}} $applyBody] 1
} {{x 1} {y 2}}
test apply-8.8 {default values} {
    apply [list {x {y 2}} $applyBody] 1 3
} {{x 1} {y 3}}
test apply-8.9 {default values} {
    apply [list {x {y 2} args} $applyBody] 1
} {{x 1} {y 2} {args {}}}
test apply-8.10 {default values} {
    apply [list {x {y 2} args} $applyBody] 1 3
} {{x 1} {y 3} {args {}}}

# Tests for leaks

test apply-9.1 {leaking internal rep} -setup {
    proc getbytes {} {
	set lines [split [memory info] "\n"]
	lindex $lines 3 3
    }
    set lam [list {} {set a 1}]
} -constraints memory -body {
    set end [getbytes]
    for {set i 0} {$i < 5} {incr i} {
	::apply [lrange $lam 0 end]
	set tmp $end
	set end [getbytes]
    }
    set leakedBytes [expr {$end - $tmp}]
} -cleanup {
    rename getbytes {}
    unset -nocomplain lam end i tmp leakedBytes
} -result 0
test apply-9.2 {leaking internal rep} -setup {
    proc getbytes {} {
	set lines [split [memory info] "\n"]
	lindex $lines 3 3
    }
} -constraints memory -body {
    set end [getbytes]
    for {set i 0} {$i < 5} {incr i} {
	::apply [list {} {set a 1}]
	set tmp $end
	set end [getbytes]
    }
    set leakedBytes [expr {$end - $tmp}]
} -cleanup {
    rename getbytes {}
    unset -nocomplain end i tmp leakedBytes
} -result 0
test apply-9.3 {leaking internal rep} -setup {
    proc getbytes {} {
	set lines [split [memory info] "\n"]
	lindex $lines 3 3
    }
} -constraints memory -body {
    set end [getbytes]
    for {set i 0} {$i < 5} {incr i} {
	set x [list {} {set a 1} ::NS::THAT::DOES::NOT::EXIST]
	catch {::apply $x}
	set x {}
	set tmp $end
	set end [getbytes]
    }
    set leakedBytes [expr {$end - $tmp}]
} -cleanup {
    rename getbytes {}
    unset -nocomplain end i x tmp leakedBytes
} -result 0

# Tests for the avoidance of recompilation

# cleanup

namespace delete testApply

::tcltest::cleanupTests
return

# Local Variables:
# mode: tcl
# fill-column: 78
# End:
1505 1506 1507 1508 1509 1510 1511 1512 1513 1514 1515 1516 1517 1518 1519 1520 1521 1522 1523 1524 1525 1526 1527 1528 1529 1530 1531 1532 1533 1534 1535 1536 1537 1538 1539 1540 1541 1542 1543 1544 1545 1546 1547 1548 1549 1550 1551 1552 1553 1554 1555 1556 1557 1558 1559 1560 1561 1562 1563 1564 1565 1566 1567 1568 1569 1570 1571 1572 1573 1574 1575 1576 1577 1578 1579 1580 1581 1582 1583 1584 1585 1586 1587 1588 1589 1590 1591 1592 1593 1594 1595 1596 1597 1598 1599 1600 1601 1602 1603 1604 1605 1606 1607 1608 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 1650 1651 1652 1653 1654 1655 1656 1657 1658 1659 1660 1661 1662 1663 1664 1665 1666 1667 1668 1669 1670 1671 1672 1673 1674 1675 1676 1677 1678 1679 1680 1681 1682 1683 1684 1685 1686 1687 1688 1689 1690 1691 1692 1693 1694 1695 1696 1697 1698 1699 1700 1701 1702 1703 1704 1705 1706 1707 1708 1709 1710 1711 1712 1713 1714 1715 1716 1717 1718 1719 1720 1721 1722 1723 1724 1725 1726 1727 1728 1729 1730 1731 1732 1733 1734 1735 1736 1737 1738 1739 1740 1741 1742 1743 1744 1745 1746 1747 1748 1749 1750 1751 1752 1753 1754 1755 1756 1757 1758 1759 1760 1761 1762 1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 1830 1831 1832 1833 1834 1835 1836 1837 1838 1839 1840 1841 1842 1843 1844 1845 1846 1847 1848 1849 1850 1851 1852 1853 1854 1855 1856 1857 1858 1859 1860 1861 1862 1863 1864 1865 1866 1867 1868 1869 1870 1871 1872 1873 1874 1875 1876 1877 1878 1879 1880 1881 1882 1883 1884 1885 1886 1887 1888 1889 1890 1891 1892 1893 1894 1895 1896 1897 1898 1899 1900 1901 1902 1903 1904 1905 1906 1907 1908 1909 1910 1911 1912 1913 1914 1915 1916 1917 1918
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 * Copyright by The HDF Group.                                               *
 * Copyright by the Board of Trustees of the University of Illinois.         *
 * All rights reserved.                                                      *
 *                                                                           *
 * This file is part of HDF5.  The full HDF5 copyright notice, including     *
 * terms governing use, modification, and redistribution, is contained in    *
 * the COPYING file, which can be found at the root of the source code       *
 * distribution tree, or in https://support.hdfgroup.org/ftp/HDF5/releases.  *
 * If you do not have access to either file, you may request a copy from     *
 * help@hdfgroup.org.                                                        *
 * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

#include "H5private.h"
#include "h5tools.h"
#include "h5tools_utils.h"
#include "h5diff.h"
#include "ph5diff.h"


/*-------------------------------------------------------------------------
 * Function: print_objname
 *
 * Purpose:  check if object name is to be printed, only when:
 *             1) verbose mode
 *             2) when diff was found (normal mode)
 *-------------------------------------------------------------------------
 */
H5_ATTR_PURE int
print_objname (diff_opt_t * opts, hsize_t nfound)
{
    return ((opts->mode_verbose || nfound) && !opts->mode_quiet) ? 1 : 0;
}

/*-------------------------------------------------------------------------
 * Function: do_print_objname
 *
 * Purpose:  print object name
 *-------------------------------------------------------------------------
 */
void
do_print_objname (const char *OBJ, const char *path1, const char *path2, diff_opt_t * opts)
{
    /* if verbose level is higher than 0, put space line before
     * displaying any object or symbolic links. This improves
     * readability of the output.
     */
    if (opts->mode_verbose_level >= 1)
        parallel_print("\n");
    parallel_print("%-7s: <%s> and <%s>\n", OBJ, path1, path2);
}

/*-------------------------------------------------------------------------
 * Function: do_print_attrname
 *
 * Purpose:  print attribute name
 *-------------------------------------------------------------------------
 */
void
do_print_attrname (const char *attr, const char *path1, const char *path2)
{
    parallel_print("%-7s: <%s> and <%s>\n", attr, path1, path2);
}

/*-------------------------------------------------------------------------
 * Function: print_warn
 *
 * Purpose:  check print warning condition.
 * Return:
 *           1 if verbose mode
 *           0 if not verbos mode
 *-------------------------------------------------------------------------
 */
static int
print_warn(diff_opt_t *opts)
{
    return ((opts->mode_verbose)) ? 1: 0;
}


#ifdef H5_HAVE_PARALLEL
/*-------------------------------------------------------------------------
 * Function: phdiff_dismiss_workers
 *
 * Purpose:  tell all workers to end.
 *
 * Return:   none
 *-------------------------------------------------------------------------
 */
void
phdiff_dismiss_workers(void)
{
    int i;
    for (i = 1; i < g_nTasks; i++)
        MPI_Send(NULL, 0, MPI_BYTE, i, MPI_TAG_END, MPI_COMM_WORLD);
}


/*-------------------------------------------------------------------------
 * Function: print_incoming_data
 *
 * Purpose:  special function that prints any output that has been sent to the manager
 *           and is currently sitting in the incoming message queue
 *
 * Return:   none
 *-------------------------------------------------------------------------
 */

static void
print_incoming_data(void)
{
    char data[PRINT_DATA_MAX_SIZE + 1];
    int  incomingMessage;
    MPI_Status Status;

    do {
        MPI_Iprobe(MPI_ANY_SOURCE, MPI_TAG_PRINT_DATA, MPI_COMM_WORLD, &incomingMessage, &Status);
        if(incomingMessage) {
            HDmemset(data, 0, PRINT_DATA_MAX_SIZE+1);
            MPI_Recv(data, PRINT_DATA_MAX_SIZE, MPI_CHAR, Status.MPI_SOURCE, MPI_TAG_PRINT_DATA, MPI_COMM_WORLD, &Status);

            HDprintf("%s", data);
        }
    } while(incomingMessage);
}
#endif

/*-------------------------------------------------------------------------
 * Function: is_valid_options
 *
 * Purpose:  check if options are valid
 *
 * Return:
 *           1 : Valid
 *           0 : Not valid
 *------------------------------------------------------------------------*/
static int
is_valid_options(diff_opt_t *opts)
{
    int ret_value = 1; /* init to valid */

    /*-----------------------------------------------
     * no -q(quiet) with -v (verbose) or -r (report) */
    if(opts->mode_quiet && (opts->mode_verbose || opts->mode_report)) {
        parallel_print("Error: -q (quiet mode) cannot be added to verbose or report modes\n");
        opts->err_stat = H5DIFF_ERR;
        H5TOOLS_GOTO_DONE(0);
    }

    /* -------------------------------------------------------
     * only allow --no-dangling-links along with --follow-symlinks */
    if(opts->no_dangle_links && !opts->follow_links) {
        parallel_print("Error: --no-dangling-links must be used along with --follow-symlinks option.\n");
        opts->err_stat = H5DIFF_ERR;
        H5TOOLS_GOTO_DONE(0);
    }

done:
    return ret_value;
}

/*-------------------------------------------------------------------------
 * Function: is_exclude_path
 *
 * Purpose:  check if 'paths' are part of exclude path list
 *
 * Return:
 *           1 - excluded path
 *           0 - not excluded path
 *------------------------------------------------------------------------*/
static int
is_exclude_path (char *path, h5trav_type_t type, diff_opt_t *opts)
{
    struct exclude_path_list *exclude_path_ptr;
    int   ret_cmp;
    int   ret_value = 0;

    /* check if exclude path option is given */
    if (!opts->exclude_path)
        H5TOOLS_GOTO_DONE(0);

    /* assign to local exclude list pointer */
    exclude_path_ptr = opts->exclude;

    /* search objects in exclude list */
    while (NULL != exclude_path_ptr) {
        /* if exclude path is is group, exclude its members as well */
        if (exclude_path_ptr->obj_type == H5TRAV_TYPE_GROUP) {
            ret_cmp = HDstrncmp(exclude_path_ptr->obj_path, path,
                                HDstrlen(exclude_path_ptr->obj_path));
            if (ret_cmp == 0) {  /* found matching members */
                size_t len_grp;

                /* check if given path belong to an excluding group, if so
                 * exclude it as well.
                 * This verifies if “/grp1/dset1” is only under “/grp1”, but
                 * not under “/grp1xxx/” group.
                 */
                len_grp = HDstrlen(exclude_path_ptr->obj_path);
                if (path[len_grp] == '/') {
                    /* belong to excluded group! */
                    ret_value = 1;
                    break;  /* while */
                }
            }
        }
        /* exclude target is not group, just exclude the object */
        else {
            ret_cmp = HDstrcmp(exclude_path_ptr->obj_path, path);
            if (ret_cmp == 0) {  /* found matching object */
                /* excluded non-group object */
                ret_value = 1;
                /* remember the type of this matching object.
                 * if it's group, it can be used for excluding its member
                 * objects in this while() loop */
                exclude_path_ptr->obj_type = type;
                break; /* while */
            }
        }
        exclude_path_ptr = exclude_path_ptr->next;
    }

done:
    return  ret_value;
}

/*-------------------------------------------------------------------------
 * Function: is_exclude_attr
 *
 * Purpose:  check if 'paths' are part of exclude path list
 *
 * Return:
 *           1 - excluded path
 *           0 - not excluded path
 *------------------------------------------------------------------------*/
static int
is_exclude_attr (const char *path, h5trav_type_t type, diff_opt_t *opts)
{
    struct exclude_path_list *exclude_ptr;
    int   ret_cmp;
    int   ret_value = 0;

    /* check if exclude attr option is given */
    if (!opts->exclude_attr_path)
        H5TOOLS_GOTO_DONE(0);

    /* assign to local exclude list pointer */
    exclude_ptr = opts->exclude_attr;

    /* search objects in exclude list */
    while (NULL != exclude_ptr) {
        /* if exclude path is is group, exclude its members as well */
        if (exclude_ptr->obj_type == H5TRAV_TYPE_GROUP) {
            ret_cmp = HDstrncmp(exclude_ptr->obj_path, path,
                                HDstrlen(exclude_ptr->obj_path));
            if (ret_cmp == 0) {  /* found matching members */
                size_t len_grp;

                /* check if given path belong to an excluding group, if so
                 * exclude it as well.
                 * This verifies if “/grp1/dset1” is only under “/grp1”, but
                 * not under “/grp1xxx/” group.
                 */
                len_grp = HDstrlen(exclude_ptr->obj_path);
                if (path[len_grp] == '/') {
                    /* belong to excluded group! */
                    ret_value = 1;
                    break;  /* while */
                }
            }
        }
        /* exclude target is not group, just exclude the object */
        else {
            ret_cmp = HDstrcmp(exclude_ptr->obj_path, path);
            if (ret_cmp == 0) {  /* found matching object */
                /* excluded non-group object */
                ret_value = 1;
                /* remember the type of this matching object.
                 * if it's group, it can be used for excluding its member
                 * objects in this while() loop */
                exclude_ptr->obj_type = type;
                break; /* while */
            }
        }
        exclude_ptr = exclude_ptr->next;
    }

done:
    return  ret_value;
}


/*-------------------------------------------------------------------------
 * Function: free_exclude_path_list
 *
 * Purpose:  free exclude object list from diff options
 *------------------------------------------------------------------------*/
static void
free_exclude_path_list(diff_opt_t *opts)
{
    struct exclude_path_list *curr = opts->exclude;
    struct exclude_path_list *next;

    while (NULL != curr) {
        next = curr->next;
        HDfree(curr);
        curr = next;
    }
}


/*-------------------------------------------------------------------------
 * Function: free_exclude_attr_list
 *
 * Purpose:  free exclude object attribute list from diff options
 *------------------------------------------------------------------------*/
static void
free_exclude_attr_list(diff_opt_t *opts)
{
    struct exclude_path_list *curr = opts->exclude_attr;
    struct exclude_path_list *next;

    while (NULL != curr) {
        next = curr->next;
        HDfree(curr);
        curr = next;
    }
}

/*-------------------------------------------------------------------------
 * Function: build_match_list
 *
 * Purpose:  get list of matching path_name from info1 and info2
 *
 * Note:
 *           Find common objects; the algorithm used for this search is the
 *           cosequential match algorithm and is described in
 *           Folk, Michael; Zoellick, Bill. (1992). File Structures. Addison-Wesley.
 *           Moved out from diff_match() to make code more flexible.
 *
 * Parameter:
 *           table_out [OUT] : return the list
 *------------------------------------------------------------------------*/
static void
build_match_list (const char *objname1, trav_info_t *info1, const char *objname2, trav_info_t *info2,
        trav_table_t ** table_out, diff_opt_t *opts)
{
    size_t   curr1 = 0;
    size_t   curr2 = 0;
    unsigned infile[2];
    char    *path1_lp = NULL;
    char    *path2_lp = NULL;
    h5trav_type_t type1_l;
    h5trav_type_t type2_l;
    size_t   path1_offset = 0;
    size_t   path2_offset = 0;
    int      cmp;
    trav_table_t *table = NULL;
    size_t   idx;

    H5TOOLS_START_DEBUG(" - errstat:%d", opts->err_stat);
    /* init */
    trav_table_init(info1->fid, &table);
    if (table == NULL) {
        H5TOOLS_INFO("Cannot create traverse table");
        H5TOOLS_GOTO_DONE_NO_RET();
    }

    /*
     * This is necessary for the case that given objects are group and
     * have different names (ex: obj1 is /grp1 and obj2 is /grp5).
     * All the objects belong to given groups are the candidates.
     * So prepare to compare paths without the group names.
     */
    H5TOOLS_DEBUG("objname1 = %s objname2 = %s ", objname1, objname2);

    /* if obj1 is not root */
    if (HDstrcmp (objname1,"/") != 0)
        path1_offset = HDstrlen(objname1);
    /* if obj2 is not root */
    if (HDstrcmp (objname2,"/") != 0)
        path2_offset = HDstrlen(objname2);

    /*--------------------------------------------------
    * build the list
    */
    while(curr1 < info1->nused && curr2 < info2->nused) {
        path1_lp = (info1->paths[curr1].path) + path1_offset;
        path2_lp = (info2->paths[curr2].path) + path2_offset;
        type1_l = info1->paths[curr1].type;
        type2_l = info2->paths[curr2].type;

        /* criteria is string compare */
        cmp = HDstrcmp(path1_lp, path2_lp);
        if(cmp == 0) {
            if(!is_exclude_path(path1_lp, type1_l, opts)) {
                infile[0] = 1;
                infile[1] = 1;
                trav_table_addflags(infile, path1_lp, info1->paths[curr1].type, table);
                /* if the two point to the same target object,
                 * mark that in table */
                if(info1->paths[curr1].fileno == info2->paths[curr2].fileno) {
                    int token_cmp;

                    if(H5Otoken_cmp(info1->fid, &info1->paths[curr1].obj_token,
                            &info2->paths[curr2].obj_token, &token_cmp) < 0) {
                        H5TOOLS_INFO("Failed to compare object tokens");
                        opts->err_stat = H5DIFF_ERR;
                        H5TOOLS_GOTO_DONE_NO_RET();
                    }

                    if(!token_cmp) {
                        idx = table->nobjs - 1;
                        table->objs[idx].is_same_trgobj = 1;
                    }
                }
            }
            curr1++;
            curr2++;
        } /* end if */
        else if(cmp < 0) {
            if(!is_exclude_path(path1_lp, type1_l, opts)) {
                infile[0] = 1;
                infile[1] = 0;
                trav_table_addflags(infile, path1_lp, info1->paths[curr1].type, table);
            }
            curr1++;
        } /* end else-if */
        else {
            if (!is_exclude_path(path2_lp, type2_l, opts)) {
                infile[0] = 0;
                infile[1] = 1;
                trav_table_addflags(infile, path2_lp, info2->paths[curr2].type, table);
            }
            curr2++;
        } /* end else */
    } /* end while */

    /* list1 did not end */
    infile[0] = 1;
    infile[1] = 0;
    while(curr1 < info1->nused) {
        path1_lp = (info1->paths[curr1].path) + path1_offset;
        type1_l = info1->paths[curr1].type;

        if(!is_exclude_path(path1_lp, type1_l, opts)) {
            trav_table_addflags(infile, path1_lp, info1->paths[curr1].type, table);
        }
        curr1++;
    } /* end while */

    /* list2 did not end */
    infile[0] = 0;
    infile[1] = 1;
    while(curr2 < info2->nused) {
        path2_lp = (info2->paths[curr2].path) + path2_offset;
        type2_l = info2->paths[curr2].type;

        if (!is_exclude_path(path2_lp, type2_l, opts)) {
            trav_table_addflags(infile, path2_lp, info2->paths[curr2].type, table);
        }
        curr2++;
    } /* end while */

    free_exclude_path_list (opts);

done:
    *table_out = table;

    H5TOOLS_ENDDEBUG("");
}


/*-------------------------------------------------------------------------
 * Function: trav_grp_objs
 *
 * Purpose:  Call back function from h5trav_visit().
 *------------------------------------------------------------------------*/
static herr_t
trav_grp_objs(const char *path, const H5O_info2_t *oinfo,
        const char *already_visited, void *udata)
{
    trav_info_visit_obj(path, oinfo, already_visited, udata);

    return 0;
}

/*-------------------------------------------------------------------------
 * Function: trav_grp_symlinks
 *
 * Purpose:  Call back function from h5trav_visit().
 *           Track and extra checkings while visiting all symbolic-links.
 *------------------------------------------------------------------------*/
static herr_t
trav_grp_symlinks(const char *path, const H5L_info2_t *linfo, void *udata)
{
    trav_info_t   *tinfo = (trav_info_t *)udata;
    diff_opt_t    *opts = (diff_opt_t *)tinfo->opts;
    h5tool_link_info_t lnk_info;
    const char    *ext_fname;
    const char    *ext_path;
    herr_t         ret_value = SUCCEED;

    H5TOOLS_START_DEBUG("");
    /* init linkinfo struct */
    HDmemset(&lnk_info, 0, sizeof(h5tool_link_info_t));

    if (!opts->follow_links) {
        trav_info_visit_lnk(path, linfo, tinfo);
        H5TOOLS_GOTO_DONE(SUCCEED);
    }

    switch(linfo->type) {
        case H5L_TYPE_SOFT:
            if((ret_value = H5tools_get_symlink_info(tinfo->fid, path, &lnk_info, opts->follow_links)) < 0) {
                H5TOOLS_GOTO_DONE(FAIL);
            }
            else if (ret_value == 0) {
                /* no dangling link option given and detect dangling link */
                tinfo->symlink_visited.dangle_link = TRUE;
                trav_info_visit_lnk(path, linfo, tinfo);
                if (opts->no_dangle_links)
                    opts->err_stat = H5DIFF_ERR; /* make dangling link is error */
                H5TOOLS_GOTO_DONE(SUCCEED);
            }

            /* check if already visit the target object */
            if(symlink_is_visited( &(tinfo->symlink_visited), linfo->type, NULL, lnk_info.trg_path))
                H5TOOLS_GOTO_DONE(SUCCEED);

            /* add this link as visited link */
            if(symlink_visit_add( &(tinfo->symlink_visited), linfo->type, NULL, lnk_info.trg_path) < 0)
                H5TOOLS_GOTO_DONE(SUCCEED);

            if(h5trav_visit(tinfo->fid, path, TRUE, TRUE,
                         trav_grp_objs,trav_grp_symlinks, tinfo, H5O_INFO_BASIC) < 0) {
                parallel_print("Error: Could not get file contents\n");
                opts->err_stat = H5DIFF_ERR;
                H5TOOLS_GOTO_ERROR(FAIL, "Error: Could not get file contents");
            }
            break;

        case H5L_TYPE_EXTERNAL:
            if ((ret_value = H5tools_get_symlink_info(tinfo->fid, path, &lnk_info, opts->follow_links)) < 0) {
                H5TOOLS_GOTO_DONE(FAIL);
            }
            else if (ret_value == 0) {
            /* no dangling link option given and detect dangling link */
                tinfo->symlink_visited.dangle_link = TRUE;
                trav_info_visit_lnk(path, linfo, tinfo);
                if (opts->no_dangle_links)
                    opts->err_stat = H5DIFF_ERR; /* make dangling link is error */
                H5TOOLS_GOTO_DONE(SUCCEED);
            }

            if(H5Lunpack_elink_val(lnk_info.trg_path, linfo->u.val_size, NULL, &ext_fname, &ext_path) < 0)
                H5TOOLS_GOTO_DONE(SUCCEED);

            /* check if already visit the target object */
            if(symlink_is_visited( &(tinfo->symlink_visited), linfo->type, ext_fname, ext_path))
                H5TOOLS_GOTO_DONE(SUCCEED);

            /* add this link as visited link */
            if(symlink_visit_add( &(tinfo->symlink_visited), linfo->type, ext_fname, ext_path) < 0)
                H5TOOLS_GOTO_DONE(SUCCEED);

            if(h5trav_visit(tinfo->fid, path, TRUE, TRUE,
                            trav_grp_objs,trav_grp_symlinks, tinfo, H5O_INFO_BASIC) < 0) {
                parallel_print("Error: Could not get file contents\n");
                opts->err_stat = H5DIFF_ERR;
                H5TOOLS_GOTO_ERROR(FAIL, "Error: Could not get file contents\n");
            }
            break;

        case H5L_TYPE_HARD:
        case H5L_TYPE_MAX:
        case H5L_TYPE_ERROR:
        default:
            parallel_print("Error: Invalid link type\n");
            opts->err_stat = H5DIFF_ERR;
            H5TOOLS_GOTO_ERROR(FAIL, "Error: Invalid link type");
            break;
    } /* end of switch */

done:
    if (lnk_info.trg_path)
        HDfree(lnk_info.trg_path);
    H5TOOLS_ENDDEBUG("");
    return ret_value;
}


/*-------------------------------------------------------------------------
 * Function: h5diff
 *
 * Purpose:  public function, can be called in an application program.
 *           return differences between 2 HDF5 files
 *
 * Return:   Number of differences found.
 *-------------------------------------------------------------------------
 */
hsize_t
h5diff(const char *fname1, const char *fname2, const char *objname1, const char *objname2, diff_opt_t *opts)
{
    hid_t         file1_id = H5I_INVALID_HID;
    hid_t         file2_id = H5I_INVALID_HID;
    hid_t         fapl1_id = H5P_DEFAULT;
    hid_t         fapl2_id = H5P_DEFAULT;
    char          filenames[2][MAX_FILENAME];
    hsize_t       nfound = 0;
    int           l_ret1 = -1;
    int           l_ret2 = -1;
    char         *obj1fullname = NULL;
    char         *obj2fullname = NULL;
    int           both_objs_grp = 0;
    /* init to group type */
    h5trav_type_t obj1type = H5TRAV_TYPE_GROUP;
    h5trav_type_t obj2type = H5TRAV_TYPE_GROUP;
    /* for single object */
    H5O_info2_t   oinfo1, oinfo2; /* object info */
    trav_info_t  *info1_obj = NULL;
    trav_info_t  *info2_obj = NULL;
    /* for group object */
    trav_info_t  *info1_grp = NULL;
    trav_info_t  *info2_grp = NULL;
    /* local pointer */
    trav_info_t  *info1_lp = NULL;
    trav_info_t  *info2_lp = NULL;
    /* link info from specified object */
    H5L_info2_t   src_linfo1;
    H5L_info2_t   src_linfo2;
    /* link info from member object */
    h5tool_link_info_t trg_linfo1;
    h5tool_link_info_t trg_linfo2;
    /* list for common objects */
    trav_table_t *match_list = NULL;
    diff_err_t    ret_value = H5DIFF_NO_ERR;

    H5TOOLS_START_DEBUG("");
    /* init filenames */
    HDmemset(filenames, 0, MAX_FILENAME * 2);
    /* init link info struct */
    HDmemset(&trg_linfo1, 0, sizeof(h5tool_link_info_t));
    HDmemset(&trg_linfo2, 0, sizeof(h5tool_link_info_t));

   /*-------------------------------------------------------------------------
    * check invalid combination of options
    *-----------------------------------------------------------------------*/
    if(!is_valid_options(opts))
        H5TOOLS_GOTO_DONE(0);

    opts->cmn_objs = 1; /* eliminate warning */
    opts->err_stat = H5DIFF_NO_ERR; /* initialize error status */

    /*-------------------------------------------------------------------------
    * open the files first; if they are not valid, no point in continuing
    *-------------------------------------------------------------------------
    */
    /* open file 1 */
    if (opts->custom_vol[0]) {
        if((fapl1_id = h5tools_get_fapl(H5P_DEFAULT, &(opts->vol_info[0]), NULL)) < 0 ) {
            parallel_print("h5diff: unable to create fapl for input file\n");
            H5TOOLS_GOTO_ERROR(H5DIFF_ERR, "unable to create input fapl\n");
        }
    }

    if((file1_id = h5tools_fopen(fname1, H5F_ACC_RDONLY, fapl1_id, FALSE, NULL, (size_t)0)) < 0) {
        parallel_print("h5diff: <%s>: unable to open file\n", fname1);
        H5TOOLS_GOTO_ERROR(H5DIFF_ERR, "<%s>: unable to open file\n", fname1);
    }
    H5TOOLS_DEBUG("file1_id = %s", fname1);

    /* open file 2 */

    if (opts->custom_vol[1]) {
        if((fapl2_id = h5tools_get_fapl(H5P_DEFAULT, &(opts->vol_info[1]), NULL)) < 0 ) {
            parallel_print("h5diff: unable to create fapl for output file\n");
            H5TOOLS_GOTO_ERROR(H5DIFF_ERR, "unable to create output fapl\n");
        }
    }

    if((file2_id = h5tools_fopen(fname2, H5F_ACC_RDONLY, fapl2_id, FALSE, NULL, (size_t)0)) < 0) {
        parallel_print("h5diff: <%s>: unable to open file\n", fname2);
        H5TOOLS_GOTO_ERROR(H5DIFF_ERR, "<%s>: unable to open file\n", fname2);
    }
    H5TOOLS_DEBUG("file2_id = %s", fname2);

    /*-------------------------------------------------------------------------
    * Initialize the info structs
    *-------------------------------------------------------------------------
    */
    trav_info_init(fname1, file1_id, &info1_obj);
    trav_info_init(fname2, file2_id, &info2_obj);

    H5TOOLS_DEBUG("trav_info_init initialized");
    /* if any object is specified */
    if (objname1) {
        /* make the given object1 fullpath, start with "/"  */