summaryrefslogtreecommitdiff
path: root/contrib/reghunt/date_based/reg_search
blob: 46602054d799cecfb5aea1034d16b23f754a0726 (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
#! /bin/bash

########################################################################
#
# File:    reg_search
# Author:  Janis Johnson <janis187@us.ibm.com>
# Date:    2002/12/15
#
# Search for a small time interval within a range of dates in which
# results for a test changed, using a binary search.  The functionality
# for getting sources, building the component to test, and running the
# test are in other scripts that are run from here.  Before the search
# begins, we verify that we get the expected behavior for the first and
# last dates.
#
# Define these in a file whose name is the argument to this script:
#   LOW_DATE:   Date string recognized by the date command (local time).
#   HIGH_DATE:  Date string recognized by the date command (local time).
#   REG_UPDATE: Pathname of script to update your source tree; returns
#               zero for success, nonzero for failure.
#   REG_BUILD:  Pathname of script to build enough of the product to run
#               the test; returns zero for success, nonzero for failure.
#   REG_TEST:   Pathname of script to run the test; returns 1 if we
#               should search later dates, 0 if we should search earlier
#               dates.
# Optional:
#   DELTA:      Search to an interval within this many seconds; default
#               is one hour (although 300 works well).
#   REG_FINISH  Pathname of script to call at the end with the two final
#               dates as arguments.
#   SKIP_LOW    If 1, skip verifying the low date of the range;
#               define this only if you're restarting and have already
#               tested the low date.
#   SKIP_HIGH   If 1, skip verifying the high date of the range;
#               define this only if you're restarting and have already
#               tested the high date.
#   FIRST_MID   Use this as the first midpoint, to avoid a midpoint that
#               is known not to build.
#   HAS_CHANGES Pathname of script to report whether the current date has
#               no differences from one of the ends of the current range
#               to skip unnecessary build and testing; default is "true".
#   VERBOSITY   Default is 0, to print only errors and final message.
#   DATE_IN_MSG If set to anything but 0, include the time and date in
#               messages.
#
#
#
# Copyright (c) 2002, 2003, 2005, 2009, 2010 Free Software Foundation, Inc.
#
# This file is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; see the file COPYING3.  If not see
# <http://www.gnu.org/licenses/>.
# 
########################################################################

########################################################################
# Functions
########################################################################

# Issue a message if its verbosity level is high enough.

msg() {
  test ${1} -gt ${VERBOSITY}  && return

  if [ "x${DATE_IN_MSG}" = "x" ]; then
    echo "${2}"
  else
    echo "`${DATE}`  ${2}"
  fi
}

# Issue an error message and exit with a non-zero status.  If there
# is a valid current range whose end points have been tested, report
# it so the user can start again from there.

error() {
  msg 0 "error: ${1}"
  test ${VALID_RANGE} -eq 1 && \
    echo "current range:"
    echo "LOW_DATE=\"${LATER_THAN}\""
    echo "HIGH_DATE=\"${EARLIER_THAN}\""
  exit 1
}

# Turn seconds since the epoch into a date we can use with source
# control tools and report to the user.

make_date() {
  MADE_DATE=`${DATE} -u +"%Y-%m-%d %H:%M %Z" --date "1970-01-01 ${1} seconds"` \
    || error "make_date: date command failed"
}

# Build the components to test using sources as of a particular date and
# run a test case.  Pass each of the scripts the date that we're
# testing; the first one needs it, the others can ignore it if they want.

process_date() {
  TEST_DATE="${1}"

  ${REG_UPDATE} "${TEST_DATE}" || error "source update failed for ${TEST_DATE}"

  # If we're already in a valid range, skip this date if there are no
  # differences from either end of the range and adjust LATER.

  if [ ${VALID_RANGE} = 1 ]; then
    ${HAS_CHANGES} "${TEST_DATE}" "${LATER_THAN}" "${EARLIER_THAN}"
    RET=$?
    case ${RET} in
    0) ;;
    1) LATER=1; return;;
    2) LATER=0; return;;
    *) error "process_date: unexpected return value from ${HAS_CHANGES}";;
    esac
  fi

  ${REG_BUILD} "${TEST_DATE}"  || error "build failed for ${TEST_DATE}"
  ${REG_TEST} "${TEST_DATE}"
  LATER=$?
}

# Perform a binary search on dates within the range specified by
# the arguments, bounded by the number of seconds in DELTA.

search_dates() {
  let LOW=$1
  let HIGH=$2
  let DIFF=HIGH-LOW

  # Get the date in the middle of the range; MID is in seconds since
  # the epoch, DATE is readable by humans and tools.  The user can
  # override the initial mid date if it is known to have problems,
  # e.g., if a build fails for that date.

  if [ ${FIRST_MID} -ne 0 ]; then
    let MID=${FIRST_MID}
  else
    let MID=LOW/2+HIGH/2
  fi

  while [ ${DIFF} -ge ${DELTA} ]; do
    make_date ${MID}
    TEST_DATE="${MADE_DATE}"

    # Test it.

    process_date "${TEST_DATE}"

    # Narrow the search based on the outcome of testing DATE.

    if [ ${LATER} -eq 1 ]; then
      msg 1 "search dates later than \"${TEST_DATE}\""
      LATER_THAN="${TEST_DATE}"
      let LOW=MID
    else
      msg 1 "search dates earlier than \"${TEST_DATE}\""
      EARLIER_THAN="${TEST_DATE}"
      let HIGH=MID
    fi

    let DIFF=HIGH-LOW
    let MID=LOW/2+HIGH/2
  done
}

########################################################################
# Main program (so to speak)
########################################################################

# If DATE isn't defined, use the default date command; the configuration
# file can override this.

if [ "x${DATE}" = "x" ]; then
  DATE=date
fi

# The error function uses this.

VALID_RANGE=0

# Process the configuration file.

if [ $# != 1 ]; then
  echo Usage: $0 config_file
  exit 1
fi

CONFIG=${1}
if [ ! -f ${CONFIG} ]; then
  error "configuration file ${CONFIG} does not exist"
fi

# OK, the config file exists.  Source it, make sure required parameters
# are defined and their files exist, and give default values to optional
# parameters.

. ${CONFIG}

test "x${REG_UPDATE}" = "x" && error "REG_UPDATE is not defined"
test "x${REG_BUILD}" = "x" && error "REG_BUILD is not defined"
test "x${REG_TEST}" = "x" && error "REG_TEST is not defined"
test -x ${REG_TEST} || error "REG_TEST is not an executable file"
test "x${SKIP_LOW}" = "x" && SKIP_LOW=0
test "x${SKIP_HIGH}" = "x" && SKIP_HIGH=0
test "x${DELTA}" = "x" && DELTA=3600
test "x${VERBOSITY}" = "x" && VERBOSITY=0
test "x${HAS_CHANGES}" = "x" && HAS_CHANGES=true
test "x${REG_FINISH}" = "x" && REG_FINISH=true

msg 2 "LOW_DATE   = ${LOW_DATE}"
msg 2 "HIGH_DATE  = ${HIGH_DATE}"
msg 2 "REG_UPDATE = ${REG_UPDATE}"
msg 2 "REG_BUILD  = ${REG_BUILD}"
msg 2 "REG_TEST   = ${REG_TEST}"
msg 2 "SKIP_LOW   = ${SKIP_LOW}"
msg 2 "SKIP_HIGH  = ${SKIP_HIGH}"
msg 2 "FIRST_MID  = ${FIRST_MID}"
msg 2 "VERBOSITY  = ${VERBOSITY}"
msg 2 "DELTA      = ${DELTA}"

# Verify that DELTA is at least two minutes.

test ${DELTA} -lt 120 && \
  error "DELTA is ${DELTA}, must be at least 120 (two minutes)"

# Change the dates into seconds since the epoch.  This uses an extension
# in GNU date.

LOW_DATE=`${DATE} +%s --date "${LOW_DATE}"` || \
  error "date command failed for \"${LOW_DATE}\""
HIGH_DATE=`${DATE} +%s --date "${HIGH_DATE}"` || \
  error "date command failed for \"${LOW_DATE}\""

# If FIRST_MID was defined, convert it and make sure it's in the range.

if [ "x${FIRST_MID}" != "x" ]; then
  FIRST_MID=`${DATE} +%s --date "${FIRST_MID}"` || \
    error "date command failed for \"${FIRST_MID}\""
  test ${FIRST_MID} -le ${LOW_DATE}  && \
    error "FIRST_MID date is earlier than LOW_DATE"
  test ${FIRST_MID} -ge ${HIGH_DATE} && \
    error "FIRST_MID is later than HIGH_DATE"
else
  FIRST_MID=0
fi 

# Keep track of the bounds of the range where the test behavior changes,
# using a human-readable version of each date.

make_date ${LOW_DATE}
LATER_THAN="${MADE_DATE}"
make_date ${HIGH_DATE}
EARLIER_THAN="${MADE_DATE}"

msg 2 "LATER_THAN   = ${LATER_THAN}"
msg 2 "EARLIER_THAN = ${EARLIER_THAN}"

# Verify that the range isn't backwards.

test ${LOW_DATE} -lt ${HIGH_DATE} || error "date range is backwards"

# Verify that the first and last date in the range get the results we
# expect.  If not, quit, because any of several things could be wrong.

if [ ${SKIP_LOW} -eq 0 ]; then
  process_date "${LATER_THAN}"
  test ${LATER} -ne 1 && \
    error "unexpected result for low date ${LATER_THAN}"
  msg 1 "result for low date is as expected"
fi

if [ ${SKIP_HIGH} -eq 0 ]; then
  process_date "${EARLIER_THAN}"
  test ${LATER} -ne 0 && \
    error "unexpected result for high date ${EARLIER_THAN}"
  msg 1 "result for high date is as expected"
fi

# Search within the range, now that we know that the end points are valid.

VALID_RANGE=1
search_dates ${LOW_DATE} ${HIGH_DATE}

# Report the range that's left to investigate.

echo "Continue search between ${LATER_THAN} and ${EARLIER_THAN}"

# Invoke the optional script to report additional information about
# changes between the two dates.

${REG_FINISH} "${LATER_THAN}" "${EARLIER_THAN}"